diff --git a/config/development.toml b/config/development.toml index b6638fe1d47..6a6fda6755e 100644 --- a/config/development.toml +++ b/config/development.toml @@ -782,3 +782,7 @@ card_networks = "Visa, AmericanExpress, Mastercard" [network_tokenization_supported_connectors] connector_list = "cybersource" + +[grpc_client.dynamic_routing_client] +host = "localhost" +port = 7000 diff --git a/crates/external_services/src/grpc_client/dynamic_routing.rs b/crates/external_services/src/grpc_client/dynamic_routing.rs index 0546d05ba7c..3264f065b51 100644 --- a/crates/external_services/src/grpc_client/dynamic_routing.rs +++ b/crates/external_services/src/grpc_client/dynamic_routing.rs @@ -14,8 +14,9 @@ use serde; use success_rate::{ success_rate_calculator_client::SuccessRateCalculatorClient, CalSuccessRateConfig, CalSuccessRateRequest, CalSuccessRateResponse, - CurrentBlockThreshold as DynamicCurrentThreshold, LabelWithStatus, - UpdateSuccessRateWindowConfig, UpdateSuccessRateWindowRequest, UpdateSuccessRateWindowResponse, + CurrentBlockThreshold as DynamicCurrentThreshold, InvalidateWindowsRequest, + InvalidateWindowsResponse, LabelWithStatus, UpdateSuccessRateWindowConfig, + UpdateSuccessRateWindowRequest, UpdateSuccessRateWindowResponse, }; use tonic::Status; #[allow( @@ -111,6 +112,11 @@ pub trait SuccessBasedDynamicRouting: dyn_clone::DynClone + Send + Sync { params: String, response: Vec, ) -> DynamicRoutingResult; + /// To invalidates the success rate routing keys + async fn invalidate_success_rate_routing_keys( + &self, + id: String, + ) -> DynamicRoutingResult; } #[async_trait::async_trait] @@ -139,9 +145,8 @@ impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient { config, }); - let mut client = self.clone(); - - let response = client + let response = self + .clone() .fetch_success_rate(request) .await .change_context(DynamicRoutingError::SuccessRateBasedRoutingFailure( @@ -179,9 +184,8 @@ impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient { config, }); - let mut client = self.clone(); - - let response = client + let response = self + .clone() .update_success_rate_window(request) .await .change_context(DynamicRoutingError::SuccessRateBasedRoutingFailure( @@ -191,6 +195,23 @@ impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient { Ok(response) } + + async fn invalidate_success_rate_routing_keys( + &self, + id: String, + ) -> DynamicRoutingResult { + let request = tonic::Request::new(InvalidateWindowsRequest { id }); + + let response = self + .clone() + .invalidate_windows(request) + .await + .change_context(DynamicRoutingError::SuccessRateBasedRoutingFailure( + "Failed to invalidate the success rate routing keys".to_string(), + ))? + .into_inner(); + Ok(response) + } } impl ForeignTryFrom for DynamicCurrentThreshold { diff --git a/crates/router/src/core/routing.rs b/crates/router/src/core/routing.rs index 94eb9bd741e..348f5229b75 100644 --- a/crates/router/src/core/routing.rs +++ b/crates/router/src/core/routing.rs @@ -7,14 +7,17 @@ use api_models::{ routing::{self as routing_types, RoutingRetrieveQuery}, }; use async_trait::async_trait; +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +use common_utils::ext_traits::AsyncExt; use diesel_models::routing_algorithm::RoutingAlgorithm; use error_stack::ResultExt; +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +use external_services::grpc_client::dynamic_routing::SuccessBasedDynamicRouting; use hyperswitch_domain_models::{mandates, payment_address}; -#[cfg(feature = "v1")] -use router_env::logger; -use router_env::metrics::add_attributes; +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +use router_env::{logger, metrics::add_attributes}; use rustc_hash::FxHashSet; -#[cfg(feature = "v1")] +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] use storage_impl::redis::cache; #[cfg(feature = "payouts")] @@ -1182,7 +1185,7 @@ pub async fn update_default_routing_config_for_profile( )) } -#[cfg(feature = "v1")] +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] pub async fn toggle_success_based_routing( state: SessionState, merchant_account: domain::MerchantAccount, @@ -1379,7 +1382,7 @@ pub async fn toggle_success_based_routing( } } -#[cfg(feature = "v1")] +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] pub async fn success_based_routing_update_configs( state: SessionState, request: routing_types::SuccessBasedRoutingConfig, @@ -1449,6 +1452,27 @@ pub async fn success_based_routing_update_configs( 1, &add_attributes([("profile_id", profile_id.get_string_repr().to_owned())]), ); + + let prefix_of_dynamic_routing_keys = helpers::generate_tenant_business_profile_id( + &state.tenant.redis_key_prefix, + profile_id.get_string_repr(), + ); + state + .grpc_client + .dynamic_routing + .success_rate_client + .as_ref() + .async_map(|sr_client| async { + sr_client + .invalidate_success_rate_routing_keys(prefix_of_dynamic_routing_keys) + .await + .change_context(errors::ApiErrorResponse::GenericNotFoundError { + message: "Failed to invalidate the routing keys".to_string(), + }) + }) + .await + .transpose()?; + Ok(service_api::ApplicationResponse::Json(new_record)) } diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 5c81db35ea9..1d7e9727cf3 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -1724,47 +1724,54 @@ impl Profile { #[cfg(all(feature = "olap", feature = "v1"))] impl Profile { pub fn server(state: AppState) -> Scope { - web::scope("/account/{account_id}/business_profile") + let mut route = web::scope("/account/{account_id}/business_profile") .app_data(web::Data::new(state)) .service( web::resource("") .route(web::post().to(profiles::profile_create)) .route(web::get().to(profiles::profiles_list)), - ) - .service( - web::scope("/{profile_id}") - .service( - web::scope("/dynamic_routing").service( - web::scope("/success_based") - .service( - web::resource("/toggle").route( - web::post().to(routing::toggle_success_based_routing), - ), - ) - .service(web::resource("/config/{algorithm_id}").route( - web::patch().to(|state, req, path, payload| { - routing::success_based_routing_update_configs( - state, req, path, payload, - ) - }), - )), - ), - ) - .service( - web::resource("") - .route(web::get().to(profiles::profile_retrieve)) - .route(web::post().to(profiles::profile_update)) - .route(web::delete().to(profiles::profile_delete)), - ) - .service( - web::resource("/toggle_extended_card_info") - .route(web::post().to(profiles::toggle_extended_card_info)), - ) - .service( - web::resource("/toggle_connector_agnostic_mit") - .route(web::post().to(profiles::toggle_connector_agnostic_mit)), + ); + + #[cfg(feature = "dynamic_routing")] + { + route = + route.service( + web::scope("/{profile_id}/dynamic_routing").service( + web::scope("/success_based") + .service( + web::resource("/toggle") + .route(web::post().to(routing::toggle_success_based_routing)), + ) + .service(web::resource("/config/{algorithm_id}").route( + web::patch().to(|state, req, path, payload| { + routing::success_based_routing_update_configs( + state, req, path, payload, + ) + }), + )), ), - ) + ); + } + + route = route.service( + web::scope("/{profile_id}") + .service( + web::resource("") + .route(web::get().to(profiles::profile_retrieve)) + .route(web::post().to(profiles::profile_update)) + .route(web::delete().to(profiles::profile_delete)), + ) + .service( + web::resource("/toggle_extended_card_info") + .route(web::post().to(profiles::toggle_extended_card_info)), + ) + .service( + web::resource("/toggle_connector_agnostic_mit") + .route(web::post().to(profiles::toggle_connector_agnostic_mit)), + ), + ); + + route } } diff --git a/crates/router/src/routes/routing.rs b/crates/router/src/routes/routing.rs index b861b54b5b5..cb7744f5200 100644 --- a/crates/router/src/routes/routing.rs +++ b/crates/router/src/routes/routing.rs @@ -1009,7 +1009,7 @@ pub async fn routing_update_default_config_for_profile( .await } -#[cfg(all(feature = "olap", feature = "v1"))] +#[cfg(all(feature = "olap", feature = "v1", feature = "dynamic_routing"))] #[instrument(skip_all)] pub async fn toggle_success_based_routing( state: web::Data, @@ -1052,7 +1052,7 @@ pub async fn toggle_success_based_routing( .await } -#[cfg(all(feature = "olap", feature = "v1"))] +#[cfg(all(feature = "olap", feature = "v1", feature = "dynamic_routing"))] #[instrument(skip_all)] pub async fn success_based_routing_update_configs( state: web::Data, diff --git a/proto/success_rate.proto b/proto/success_rate.proto index 8018f6d5fe4..38e56e36c0f 100644 --- a/proto/success_rate.proto +++ b/proto/success_rate.proto @@ -1,57 +1,68 @@ syntax = "proto3"; package success_rate; - service SuccessRateCalculator { - rpc FetchSuccessRate (CalSuccessRateRequest) returns (CalSuccessRateResponse); - - rpc UpdateSuccessRateWindow (UpdateSuccessRateWindowRequest) returns (UpdateSuccessRateWindowResponse); - } - - // API-1 types - message CalSuccessRateRequest { - string id = 1; - string params = 2; - repeated string labels = 3; - CalSuccessRateConfig config = 4; - } - - message CalSuccessRateConfig { - uint32 min_aggregates_size = 1; - double default_success_rate = 2; - } - - message CalSuccessRateResponse { - repeated LabelWithScore labels_with_score = 1; - } - - message LabelWithScore { - double score = 1; - string label = 2; - } +service SuccessRateCalculator { + rpc FetchSuccessRate (CalSuccessRateRequest) returns (CalSuccessRateResponse); + + rpc UpdateSuccessRateWindow (UpdateSuccessRateWindowRequest) returns (UpdateSuccessRateWindowResponse); + + rpc InvalidateWindows (InvalidateWindowsRequest) returns (InvalidateWindowsResponse); +} + +// API-1 types +message CalSuccessRateRequest { + string id = 1; + string params = 2; + repeated string labels = 3; + CalSuccessRateConfig config = 4; +} + +message CalSuccessRateConfig { + uint32 min_aggregates_size = 1; + double default_success_rate = 2; +} + +message CalSuccessRateResponse { + repeated LabelWithScore labels_with_score = 1; +} + +message LabelWithScore { + double score = 1; + string label = 2; +} // API-2 types - message UpdateSuccessRateWindowRequest { - string id = 1; - string params = 2; - repeated LabelWithStatus labels_with_status = 3; - UpdateSuccessRateWindowConfig config = 4; - } - - message LabelWithStatus { - string label = 1; - bool status = 2; - } - - message UpdateSuccessRateWindowConfig { - uint32 max_aggregates_size = 1; - CurrentBlockThreshold current_block_threshold = 2; - } - - message CurrentBlockThreshold { - optional uint64 duration_in_mins = 1; - uint64 max_total_count = 2; - } - - message UpdateSuccessRateWindowResponse { - string message = 1; - } \ No newline at end of file +message UpdateSuccessRateWindowRequest { + string id = 1; + string params = 2; + repeated LabelWithStatus labels_with_status = 3; + UpdateSuccessRateWindowConfig config = 4; +} + +message LabelWithStatus { + string label = 1; + bool status = 2; +} + +message UpdateSuccessRateWindowConfig { + uint32 max_aggregates_size = 1; + CurrentBlockThreshold current_block_threshold = 2; +} + +message CurrentBlockThreshold { + optional uint64 duration_in_mins = 1; + uint64 max_total_count = 2; +} + +message UpdateSuccessRateWindowResponse { + string message = 1; +} + + // API-3 types +message InvalidateWindowsRequest { + string id = 1; +} + +message InvalidateWindowsResponse { + string message = 1; +} \ No newline at end of file