From 952c62c2188f4511c027bbc79023636d9e9b68d8 Mon Sep 17 00:00:00 2001 From: Noor Date: Wed, 12 Mar 2025 20:05:27 +0000 Subject: [PATCH] feat: add filtering and listing functionality for app nodes --- Cargo.lock | 2 +- src/data.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/grpc.rs | 37 +++++++++++++++++++++++++++++++++++-- 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 655bc74..6aef7a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -420,7 +420,7 @@ dependencies = [ [[package]] name = "detee-shared" version = "0.1.0" -source = "git+ssh://git@gitea.detee.cloud/noormohammedb/detee-shared?branch=stable_01#8230e1f831d0f88fe5203646e323bf215a825574" +source = "git+ssh://git@gitea.detee.cloud/noormohammedb/detee-shared?branch=stable_01#a9dcfbe9fec5a86cd69aa2853db124aae9b85598" dependencies = [ "base64", "prost", diff --git a/src/data.rs b/src/data.rs index c6e76b4..0d22f82 100644 --- a/src/data.rs +++ b/src/data.rs @@ -15,6 +15,8 @@ use tokio::sync::oneshot::Sender as OneshotSender; use detee_shared::sgx::pb::brain::brain_message_app; use detee_shared::sgx::pb::brain::AppContract as AppContractPB; +use detee_shared::sgx::pb::brain::AppNodeFilters; +use detee_shared::sgx::pb::brain::AppNodeListResp; use detee_shared::sgx::pb::brain::AppNodeResources; use detee_shared::sgx::pb::brain::AppResource as AppResourcePB; use detee_shared::sgx::pb::brain::BrainMessageApp; @@ -266,6 +268,21 @@ pub struct AppNode { pub offline_minutes: u64, } +impl From for AppNodeListResp { + fn from(value: AppNode) -> Self { + Self { + operator: value.operator_wallet, + node_pubkey: value.node_pubkey, + country: value.country, + region: value.region, + city: value.city, + ip: value.ip, + price: value.price, + reports: Vec::new(), + } + } +} + #[derive(Default, Serialize, Deserialize)] pub struct BrainData { accounts: DashMap, @@ -1199,6 +1216,41 @@ impl BrainData { }); } + pub fn find_app_nodes_by_filters(&self, filters: &AppNodeFilters) -> Vec { + let nodes = self.app_nodes.read().unwrap(); + nodes + .iter() + .filter(|n| { + n.avail_vcpus >= filters.vcpus + && n.avail_mem_mb >= filters.memory_mb + && n.avail_storage_mb >= filters.storage_mb + && (filters.country.is_empty() || (n.country == filters.country)) + && (filters.city.is_empty() || (n.city == filters.city)) + && (filters.region.is_empty() || (n.region == filters.region)) + && (filters.ip.is_empty() || (n.ip == filters.ip)) + }) + .cloned() + .collect() + } + + // TODO: sort by rating + pub fn get_one_app_node_by_filters(&self, filters: &AppNodeFilters) -> Option { + let nodes = self.app_nodes.read().unwrap(); + nodes + .iter() + .find(|n| { + n.avail_vcpus >= filters.vcpus + && n.avail_mem_mb >= filters.memory_mb + && n.avail_storage_mb >= filters.storage_mb + && (filters.country.is_empty() || (n.country == filters.country)) + && (filters.city.is_empty() || (n.city == filters.city)) + && (filters.region.is_empty() || (n.region == filters.region)) + && (filters.ip.is_empty() || (n.ip == filters.ip)) + && (filters.node_pubkey.is_empty() || (n.node_pubkey == filters.node_pubkey)) + }) + .cloned() + } + pub fn find_app_contract_by_uuid(&self, uuid: &str) -> Result { let contracts = self.app_contracts.read().unwrap(); contracts diff --git a/src/grpc.rs b/src/grpc.rs index 371ca0d..c4113fb 100644 --- a/src/grpc.rs +++ b/src/grpc.rs @@ -17,8 +17,8 @@ use tonic::{Request, Response, Status, Streaming}; use detee_shared::sgx::pb::brain::brain_app_cli_server::BrainAppCli; use detee_shared::sgx::pb::brain::brain_app_daemon_server::BrainAppDaemon; use detee_shared::sgx::pb::brain::{ - AppContract, BrainMessageApp, DaemonMessageApp, DelAppReq, ListAppContractsReq, NewAppReq, - NewAppRes, RegisterAppNodeReq, + AppContract, AppNodeFilters, AppNodeListResp, BrainMessageApp, DaemonMessageApp, DelAppReq, + ListAppContractsReq, NewAppReq, NewAppRes, RegisterAppNodeReq, }; const ADMIN_ACCOUNTS: &[&str] = &[ "x52w7jARC5erhWWK65VZmjdGXzBK6ZDgfv1A283d8XK", @@ -457,6 +457,7 @@ trait PubkeyGetter { #[tonic::async_trait] impl BrainAppCli for BrainAppCliMock { type ListAppContractsStream = Pin> + Send>>; + type ListAppNodesStream = Pin> + Send>>; async fn deploy_app( &self, @@ -516,6 +517,37 @@ impl BrainAppCli for BrainAppCliMock { Box::pin(output_stream) as Self::ListAppContractsStream )) } + + async fn list_app_nodes( + &self, + req: tonic::Request, + ) -> Result, Status> { + let req = check_sig_from_req(req)?; + info!("CLI requested ListAppNodes: {req:?}"); + let nodes = self.data.find_app_nodes_by_filters(&req); + let (tx, rx) = mpsc::channel(6); + tokio::spawn(async move { + for node in nodes { + let _ = tx.send(Ok(node.into())).await; + } + }); + let output_stream = ReceiverStream::new(rx); + Ok(Response::new(Box::pin(output_stream))) + } + + async fn get_one_app_node( + &self, + req: tonic::Request, + ) -> Result, Status> { + let req = check_sig_from_req(req)?; + info!("CLI requested GetOneAppNode: {req:?}"); + match self.data.get_one_app_node_by_filters(&req) { + Some(node) => Ok(Response::new(node.into())), + None => Err(Status::not_found( + "Could not find any node based on your search criteria", + )), + } + } } #[tonic::async_trait] @@ -681,6 +713,7 @@ impl_pubkey_getter!(DelAppReq, admin_pubkey); impl_pubkey_getter!(ListAppContractsReq, admin_pubkey); impl_pubkey_getter!(RegisterAppNodeReq); +impl_pubkey_getter!(AppNodeFilters); fn check_sig_from_req(req: Request) -> Result { let time = match req.metadata().get("timestamp") {