WIP on implementing redirect

Refactors brain connection logic to use custom endpoint while runtime
Implements redirect handling in the `create_vm` function to gracefully handle server moves by reconnecting to the new URL
This commit is contained in:
Noor 2025-06-11 19:12:14 +05:30
parent 51d50ff496
commit 41d9bd104f
Signed by: noormohammedb
GPG Key ID: D83EFB8B3B967146
5 changed files with 43 additions and 10 deletions

@ -309,20 +309,22 @@ impl Config {
pub fn get_brain_info() -> (String, String) { pub fn get_brain_info() -> (String, String) {
match Self::init_config().network.as_str() { match Self::init_config().network.as_str() {
"staging" => ("https://184.107.169.199:49092".to_string(), "staging-brain".to_string()), "staging" => ("https://10.254.254.8:31337".to_string(), "staging-brain".to_string()),
"localhost" => ("https://localhost:31337".to_string(), "staging-brain".to_string()), "localhost" => ("https://localhost:31337".to_string(), "staging-brain".to_string()),
_ => ("https://173.234.17.2:39477".to_string(), "testnet-brain".to_string()), _ => ("https://173.234.17.2:39477".to_string(), "testnet-brain".to_string()),
} }
} }
pub async fn get_brain_channel() -> Result<tonic::transport::Channel, Error> { pub async fn connect_brain_channel(
let (brain_url, brain_san) = Self::get_brain_info(); brain_url: String,
) -> Result<tonic::transport::Channel, Error> {
use hyper_rustls::HttpsConnectorBuilder; use hyper_rustls::HttpsConnectorBuilder;
use rustls::pki_types::pem::PemObject; use rustls::pki_types::pem::PemObject;
use rustls::pki_types::CertificateDer; use rustls::pki_types::CertificateDer;
use rustls::{ClientConfig, RootCertStore}; use rustls::{ClientConfig, RootCertStore};
let brain_san = Config::get_brain_info().1;
let mut detee_root_ca_store = RootCertStore::empty(); let mut detee_root_ca_store = RootCertStore::empty();
detee_root_ca_store detee_root_ca_store
.add(CertificateDer::from_pem_file(Config::get_root_ca_path()?).map_err(|e| { .add(CertificateDer::from_pem_file(Config::get_root_ca_path()?).map_err(|e| {

@ -1 +1,2 @@
pub const HRATLS_APP_PORT: u32 = 34500; pub const HRATLS_APP_PORT: u32 = 34500;
pub const MAX_REDIRECTS: u16 = 3;

@ -35,7 +35,8 @@ pub enum Error {
} }
async fn client() -> Result<BrainGeneralCliClient<Channel>, Error> { async fn client() -> Result<BrainGeneralCliClient<Channel>, Error> {
Ok(BrainGeneralCliClient::new(Config::get_brain_channel().await?)) let default_brain_url = Config::get_brain_info().0;
Ok(BrainGeneralCliClient::new(Config::connect_brain_channel(default_brain_url).await?))
} }
pub async fn get_balance(account: &str) -> Result<AccountBalance, Error> { pub async fn get_balance(account: &str) -> Result<AccountBalance, Error> {

@ -66,7 +66,8 @@ impl crate::HumanOutput for AppContract {
} }
async fn client() -> Result<BrainAppCliClient<Channel>> { async fn client() -> Result<BrainAppCliClient<Channel>> {
Ok(BrainAppCliClient::new(Config::get_brain_channel().await?)) let default_brain_url = Config::get_brain_info().0;
Ok(BrainAppCliClient::new(Config::connect_brain_channel(default_brain_url).await?))
} }
pub async fn new_app(app_deploy_config: AppDeployConfig) -> Result<NewAppRes> { pub async fn new_app(app_deploy_config: AppDeployConfig) -> Result<NewAppRes> {

@ -4,6 +4,7 @@ pub mod proto {
} }
use crate::config::Config; use crate::config::Config;
use crate::constants::MAX_REDIRECTS;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use log::{debug, info, warn}; use log::{debug, info, warn};
use proto::{ use proto::{
@ -41,6 +42,8 @@ pub enum Error {
CorruptedRootCa(#[from] std::io::Error), CorruptedRootCa(#[from] std::io::Error),
#[error("Internal app error: could not parse Brain URL")] #[error("Internal app error: could not parse Brain URL")]
CorruptedBrainUrl, CorruptedBrainUrl,
#[error("Max redirects exceeded: {0}")]
MaxRedirectsExceeded(String),
} }
impl crate::HumanOutput for VmContract { impl crate::HumanOutput for VmContract {
@ -84,7 +87,14 @@ impl crate::HumanOutput for VmNodeListResp {
} }
async fn client() -> Result<BrainVmCliClient<Channel>, Error> { async fn client() -> Result<BrainVmCliClient<Channel>, Error> {
Ok(BrainVmCliClient::new(Config::get_brain_channel().await?)) let default_brain_url = Config::get_brain_info().0;
Ok(BrainVmCliClient::new(Config::connect_brain_channel(default_brain_url).await?))
}
async fn client_from_endpoint(
reconnect_endpoint: String,
) -> Result<BrainVmCliClient<Channel>, Error> {
Ok(BrainVmCliClient::new(Config::connect_brain_channel(reconnect_endpoint).await?))
} }
fn sign_request<T: std::fmt::Debug>(req: T) -> Result<Request<T>, Error> { fn sign_request<T: std::fmt::Debug>(req: T) -> Result<Request<T>, Error> {
@ -130,10 +140,28 @@ pub async fn get_one_node(req: VmNodeFilters) -> Result<VmNodeListResp, Error> {
pub async fn create_vm(req: NewVmReq) -> Result<NewVmResp, Error> { pub async fn create_vm(req: NewVmReq) -> Result<NewVmResp, Error> {
let mut client = client().await?; let mut client = client().await?;
debug!("Sending NewVmReq to brain: {req:?}"); debug!("Sending NewVmReq to brain: {req:?}");
match client.new_vm(sign_request(req)?).await { for attempt in 0..MAX_REDIRECTS {
Ok(resp) => Ok(resp.into_inner()), match client.new_vm(sign_request(req.clone())?).await {
Err(e) => Err(e.into()), Ok(resp) => return Ok(resp.into_inner()),
Err(status)
if status.code() == tonic::Code::Unavailable
&& status.message() == "moved"
&& status.metadata().contains_key("location") =>
{
let redirect_url = status
.metadata()
.get("location")
.and_then(|v| v.to_str().ok().map(|s| format!("https://{s}")))
.unwrap_or_default();
// TODO: change this println to log::info!()
println!("{attempt}) server moved to a different URL, trying to reconnect... ({redirect_url})");
client = client_from_endpoint(redirect_url).await?;
continue;
} }
Err(e) => return Err(e.into()),
};
}
Err(Error::MaxRedirectsExceeded(MAX_REDIRECTS.to_string()))
} }
pub async fn list_contracts(req: ListVmContractsReq) -> Result<Vec<VmContract>, Error> { pub async fn list_contracts(req: ListVmContractsReq) -> Result<Vec<VmContract>, Error> {