diff --git a/src/api.rs b/src/api.rs index df91a78..8c6fd34 100644 --- a/src/api.rs +++ b/src/api.rs @@ -2,7 +2,7 @@ use poem_openapi::{param::Path, payload::Json, ApiResponse, OpenApi}; use crate::{ backend::{AppControllerBackend, BackendError}, - types::{App, AppConfig, AppId, AppStatus}, + types::{App, AppConfig, AppId, AppStatus, SocketAddr}, }; pub struct Api(B); @@ -85,6 +85,19 @@ enum GetAllAppsResponse { InternalError(Json), } +#[derive(ApiResponse)] +enum GetAppAddrResponse { + /// The address and port of the app were found successfully. + #[oai(status = 200)] + Ok(Json), + /// The app could not be found. + #[oai(status = 404)] + NotFound, + /// The app could not be found because of an internal error. + #[oai(status = 500)] + InternalError, +} + #[OpenApi] impl Api { /// Create new app @@ -148,4 +161,17 @@ impl Api { Err(err) => GetAllAppsResponse::InternalError(Json(err.to_string())), } } + + /// Get internal app address + #[oai(path = "/app/:id/addr", method = "get")] + async fn get_app_addr(&self, id: Path) -> GetAppAddrResponse { + match self.0.get_app_addr(id.0).await { + Ok((addr, port)) => GetAppAddrResponse::Ok(Json(SocketAddr { + ip: addr.to_string(), + port, + })), + Err(BackendError::NotFound) => GetAppAddrResponse::NotFound, + Err(BackendError::InternalError(_)) => GetAppAddrResponse::InternalError, + } + } } diff --git a/src/backend.rs b/src/backend.rs index 969181b..fb04b5f 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -8,7 +8,7 @@ pub use kubernetes::KubernetesBackend; #[cfg(test)] pub use mock::MockBackend; -use std::future::Future; +use std::{future::Future, net::IpAddr}; use crate::types::{App, AppConfig, AppId, AppStatus}; @@ -34,4 +34,10 @@ pub trait AppControllerBackend: Send + Sync { /// Get all apps currently managed by the controller. fn get_all_apps(&self) -> impl Future, BackendError>> + Send; + + /// Get the address and port of the app with the given ID. + fn get_app_addr( + &self, + id: AppId, + ) -> impl Future> + Send; } diff --git a/src/backend/kubernetes.rs b/src/backend/kubernetes.rs index 0a0c5db..3c3646e 100644 --- a/src/backend/kubernetes.rs +++ b/src/backend/kubernetes.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, str::FromStr}; +use std::{collections::BTreeMap, net::IpAddr, str::FromStr}; use crate::types::{App, AppConfig, AppId, AppStatus, InteractionModel}; use k8s_openapi::{ @@ -308,4 +308,32 @@ impl AppControllerBackend for KubernetesBackend { Ok(apps) } + + async fn get_app_addr(&self, id: AppId) -> Result<(IpAddr, u16), BackendError> { + let service_api: Api = Api::default_namespaced(self.client.clone()); + let list_params = ListParams::default().labels(&format!("app-controller-id={}", id)); + let list = service_api.list(&list_params).await?; + + // There should be only one service with the given ID. + let service = list + .items + .into_iter() + .next() + .ok_or(BackendError::NotFound)?; + + // Get the IP address of the service. + let raw = service + .spec + .ok_or(BackendError::InternalError( + "Missing spec in service".to_string(), + ))? + .cluster_ip + .ok_or(BackendError::InternalError( + "Missing cluster IP in service".to_string(), + ))?; + + IpAddr::from_str(&raw) + .map(|ip| (ip, 5911)) + .map_err(|e| BackendError::InternalError(e.to_string())) + } } diff --git a/src/backend/mock.rs b/src/backend/mock.rs index 89d69c3..02e7a1d 100644 --- a/src/backend/mock.rs +++ b/src/backend/mock.rs @@ -1,5 +1,6 @@ use std::{ collections::HashMap, + net::IpAddr, sync::{ atomic::{AtomicU64, Ordering}, RwLock, @@ -80,4 +81,8 @@ impl AppControllerBackend for MockBackend { async fn get_all_apps(&self) -> Result, BackendError> { Ok(self.apps.read().unwrap().values().cloned().collect()) } + + async fn get_app_addr(&self, _id: AppId) -> Result<(IpAddr, u16), BackendError> { + todo!() + } } diff --git a/src/types.rs b/src/types.rs index dcecbe3..3bf3d1b 100644 --- a/src/types.rs +++ b/src/types.rs @@ -45,3 +45,13 @@ pub struct App { /// Current status of the app. pub config: AppConfig, } + +pub type IpAddr = String; + +/// Address and port of a app. +#[derive(Debug, Clone, Object)] +#[oai(read_only_all = true)] +pub struct SocketAddr { + pub ip: IpAddr, + pub port: u16, +}