Compare commits
	
		
			6 Commits
		
	
	
		
			810c1b1966
			...
			57d3807a17
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 57d3807a17 | |||
| 929530a4c5 | |||
| 4c647eef6a | |||
| 6a85acda9e | |||
| 32b587c6c5 | |||
| 0fc9b8003a | 
							
								
								
									
										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_app#da0f3269a31e0ebfb7328e2115e212aabe4d984a" | source = "git+ssh://git@gitea.detee.cloud/testnet/proto?branch=surreal_brain#d6ca058d2de78b5257517034bca2b2c7d5929db8" | ||||||
| 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_app" } | detee-shared = { git = "ssh://git@gitea.detee.cloud/testnet/proto", branch = "surreal_brain" } | ||||||
| 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" | ||||||
|  | |||||||
| @ -1,91 +0,0 @@ | |||||||
| 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; |  | ||||||
| @ -17,5 +17,10 @@ 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!( | ||||||
|                 "$vm_nodes = (select id from vm_node where operator = account:{account}).id;
 |                 "LET $vm_nodes = (select id from vm_node where operator = account:{account}).id;
 | ||||||
|                 $app_nodes = (select id from app_node where operator = account:{account}).id; |                 LET $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,7 +30,27 @@ 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 offline_minutes: u64, |     pub connected_at: Datetime, | ||||||
|  |     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)] | ||||||
| @ -51,14 +71,6 @@ 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, | ||||||
| @ -75,13 +87,10 @@ 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, | ||||||
| @ -115,7 +124,7 @@ impl VmNodeWithReports { | |||||||
|         if !filters.ip.is_empty() { |         if !filters.ip.is_empty() { | ||||||
|             query += &format!("&& ip = '{}' ", filters.ip); |             query += &format!("&& ip = '{}' ", filters.ip); | ||||||
|         } |         } | ||||||
|         query += ";"; |         query += " && connected_at > disconnected_at;"; | ||||||
|         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) | ||||||
| @ -191,7 +200,46 @@ impl NewVmReq { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub async fn submit(self, db: &Surreal<Client>) -> Result<(), Error> { |     pub async fn submit(self, db: &Surreal<Client>) -> Result<(), Error> { | ||||||
|         let _: Vec<Self> = db.insert(NEW_VM_REQ).relation(self).await?; |         let locked_nano = self.locked_nano; | ||||||
|  |         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(()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -366,9 +414,12 @@ 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(()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -773,7 +824,8 @@ 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, | ||||||
|                 offline_minutes: old_node.offline_minutes, |                 disconnected_at: Datetime::default(), | ||||||
|  |                 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<VmContract, Status>> + Send>>; |     type RegisterVmNodeStream = Pin<Box<dyn Stream<Item = Result<DeleteVmReq, Status>> + Send>>; | ||||||
| 
 | 
 | ||||||
|     async fn register_vm_node( |     async fn register_vm_node( | ||||||
|         &self, |         &self, | ||||||
| @ -53,17 +53,18 @@ 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, | ||||||
|             offline_minutes: 0, |             disconnected_at: surrealdb::sql::Datetime::default(), | ||||||
|  |             connected_at: surrealdb::sql::Datetime::default(), | ||||||
|         } |         } | ||||||
|         .register(&self.db) |         .register(&self.db) | ||||||
|         .await?; |         .await?; | ||||||
| 
 | 
 | ||||||
|         info!("Sending existing contracts to {}", req.node_pubkey); |         info!("Sending deleted contracts to {}", req.node_pubkey); | ||||||
|         let contracts = db::ActiveVmWithNode::list_by_node(&self.db, &req.node_pubkey).await?; |         let deleted_vms = db::DeletedVm::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 contract in contracts { |             for deleted_vm in deleted_vms { | ||||||
|                 let _ = tx.send(Ok(contract.into())).await; |                 let _ = tx.send(Ok(deleted_vm.into())).await; | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|         let output_stream = ReceiverStream::new(rx); |         let output_stream = ReceiverStream::new(rx); | ||||||
| @ -83,6 +84,7 @@ 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); | ||||||
|         { |         { | ||||||
| @ -194,7 +196,8 @@ impl BrainVmDaemon for VmDaemonServer { | |||||||
|                     _ => {} |                     _ => {} | ||||||
|                 }, |                 }, | ||||||
|                 Err(e) => { |                 Err(e) => { | ||||||
|                     log::warn!("Daemon disconnected: {e:?}"); |                     log::warn!("Daemon disconnected for {pubkey}: {e:?}"); | ||||||
|  |                     let _ = db::VmNode::set_offline(&self.db, &pubkey).await; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								surql/README.md
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										2
									
								
								surql/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | |||||||
|  | This is actually SurrealQL (`.surql`), but the files have the `.sql` | ||||||
|  | extension to enable syntax coloring. | ||||||
							
								
								
									
										12
									
								
								surql/brain-timer.sh
									
									
									
									
									
										Executable file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										12
									
								
								surql/brain-timer.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | #!/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 | ||||||
							
								
								
									
										6
									
								
								surql/detee-brain-contracts.service
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										6
									
								
								surql/detee-brain-contracts.service
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | [Unit] | ||||||
|  | Description=Process brain contracts | ||||||
|  | 
 | ||||||
|  | [Service] | ||||||
|  | Type=oneshot | ||||||
|  | ExecStart=/etc/detee/brain/brain-timer.sh | ||||||
							
								
								
									
										9
									
								
								surql/detee-brain-contracts.timer
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										9
									
								
								surql/detee-brain-contracts.timer
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | |||||||
|  | [Unit] | ||||||
|  | Description=Run detee-brain-contracts.service every minute | ||||||
|  | 
 | ||||||
|  | [Timer] | ||||||
|  | OnCalendar=*-*-* *:*:00 | ||||||
|  | Persistent=true | ||||||
|  | 
 | ||||||
|  | [Install] | ||||||
|  | WantedBy=timers.target | ||||||
							
								
								
									
										27
									
								
								surql/functions.sql
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										27
									
								
								surql/functions.sql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | |||||||
|  | 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; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| @ -18,7 +18,8 @@ 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 offline_minutes ON TABLE vm_node TYPE int; | DEFINE FIELD connected_at ON TABLE vm_node TYPE datetime; | ||||||
|  | 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; | ||||||
| @ -74,7 +75,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; | DEFINE FIELD deleted_at ON TABLE deleted_vm TYPE datetime DEFAULT time::now(); | ||||||
| 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; | ||||||
							
								
								
									
										160
									
								
								surql/testing/data.sql
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										160
									
								
								surql/testing/data.sql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,160 @@ | |||||||
|  | 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 | ||||||
|  | }; | ||||||
							
								
								
									
										33
									
								
								surql/testing/run_test1.sh
									
									
									
									
									
										Executable file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										33
									
								
								surql/testing/run_test1.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,33 @@ | |||||||
|  | #!/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 | ||||||
							
								
								
									
										19
									
								
								surql/testing/verification.sql
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										19
									
								
								surql/testing/verification.sql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | 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.") | ||||||
|  | }; | ||||||
							
								
								
									
										29
									
								
								surql/timer.sql
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										29
									
								
								surql/timer.sql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | |||||||
|  | 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