feat: add filtering and listing functionality for app nodes

This commit is contained in:
Noor 2025-03-12 20:05:27 +00:00
parent 1fa37a0bb9
commit 952c62c218
Signed by: noormohammedb
GPG Key ID: E424C39E19EFD7DF
3 changed files with 88 additions and 3 deletions

2
Cargo.lock generated

@ -420,7 +420,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/noormohammedb/detee-shared?branch=stable_01#8230e1f831d0f88fe5203646e323bf215a825574" source = "git+ssh://git@gitea.detee.cloud/noormohammedb/detee-shared?branch=stable_01#a9dcfbe9fec5a86cd69aa2853db124aae9b85598"
dependencies = [ dependencies = [
"base64", "base64",
"prost", "prost",

@ -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::brain_message_app;
use detee_shared::sgx::pb::brain::AppContract as AppContractPB; 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::AppNodeResources;
use detee_shared::sgx::pb::brain::AppResource as AppResourcePB; use detee_shared::sgx::pb::brain::AppResource as AppResourcePB;
use detee_shared::sgx::pb::brain::BrainMessageApp; use detee_shared::sgx::pb::brain::BrainMessageApp;
@ -266,6 +268,21 @@ pub struct AppNode {
pub offline_minutes: u64, pub offline_minutes: u64,
} }
impl From<AppNode> 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)] #[derive(Default, Serialize, Deserialize)]
pub struct BrainData { pub struct BrainData {
accounts: DashMap<String, AccountData>, accounts: DashMap<String, AccountData>,
@ -1199,6 +1216,41 @@ impl BrainData {
}); });
} }
pub fn find_app_nodes_by_filters(&self, filters: &AppNodeFilters) -> Vec<AppNode> {
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<AppNode> {
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<AppContract, Error> { pub fn find_app_contract_by_uuid(&self, uuid: &str) -> Result<AppContract, Error> {
let contracts = self.app_contracts.read().unwrap(); let contracts = self.app_contracts.read().unwrap();
contracts contracts

@ -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_cli_server::BrainAppCli;
use detee_shared::sgx::pb::brain::brain_app_daemon_server::BrainAppDaemon; use detee_shared::sgx::pb::brain::brain_app_daemon_server::BrainAppDaemon;
use detee_shared::sgx::pb::brain::{ use detee_shared::sgx::pb::brain::{
AppContract, BrainMessageApp, DaemonMessageApp, DelAppReq, ListAppContractsReq, NewAppReq, AppContract, AppNodeFilters, AppNodeListResp, BrainMessageApp, DaemonMessageApp, DelAppReq,
NewAppRes, RegisterAppNodeReq, ListAppContractsReq, NewAppReq, NewAppRes, RegisterAppNodeReq,
}; };
const ADMIN_ACCOUNTS: &[&str] = &[ const ADMIN_ACCOUNTS: &[&str] = &[
"x52w7jARC5erhWWK65VZmjdGXzBK6ZDgfv1A283d8XK", "x52w7jARC5erhWWK65VZmjdGXzBK6ZDgfv1A283d8XK",
@ -457,6 +457,7 @@ trait PubkeyGetter {
#[tonic::async_trait] #[tonic::async_trait]
impl BrainAppCli for BrainAppCliMock { impl BrainAppCli for BrainAppCliMock {
type ListAppContractsStream = Pin<Box<dyn Stream<Item = Result<AppContract, Status>> + Send>>; type ListAppContractsStream = Pin<Box<dyn Stream<Item = Result<AppContract, Status>> + Send>>;
type ListAppNodesStream = Pin<Box<dyn Stream<Item = Result<AppNodeListResp, Status>> + Send>>;
async fn deploy_app( async fn deploy_app(
&self, &self,
@ -516,6 +517,37 @@ impl BrainAppCli for BrainAppCliMock {
Box::pin(output_stream) as Self::ListAppContractsStream Box::pin(output_stream) as Self::ListAppContractsStream
)) ))
} }
async fn list_app_nodes(
&self,
req: tonic::Request<AppNodeFilters>,
) -> Result<tonic::Response<Self::ListAppNodesStream>, 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<AppNodeFilters>,
) -> Result<tonic::Response<AppNodeListResp>, 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] #[tonic::async_trait]
@ -681,6 +713,7 @@ impl_pubkey_getter!(DelAppReq, admin_pubkey);
impl_pubkey_getter!(ListAppContractsReq, admin_pubkey); impl_pubkey_getter!(ListAppContractsReq, admin_pubkey);
impl_pubkey_getter!(RegisterAppNodeReq); impl_pubkey_getter!(RegisterAppNodeReq);
impl_pubkey_getter!(AppNodeFilters);
fn check_sig_from_req<T: std::fmt::Debug + PubkeyGetter>(req: Request<T>) -> Result<T, Status> { fn check_sig_from_req<T: std::fmt::Debug + PubkeyGetter>(req: Request<T>) -> Result<T, Status> {
let time = match req.metadata().get("timestamp") { let time = match req.metadata().get("timestamp") {