diff --git a/config/config.example.toml b/config/config.example.toml index 81516ee8b6bf..30f334303785 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -639,6 +639,7 @@ sdk_eligible_payment_methods = "card" [multitenancy] enabled = false +global_tenant = { schema = "public", redis_key_prefix = "" } [multitenancy.tenants] -public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = ""} # schema -> Postgres db schema, redis_key_prefix -> redis key distinguisher, base_url -> url of the tenant \ No newline at end of file +public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} # schema -> Postgres db schema, redis_key_prefix -> redis key distinguisher, base_url -> url of the tenant \ No newline at end of file diff --git a/config/deployments/env_specific.toml b/config/deployments/env_specific.toml index 9ab790b8ee8e..162444c90988 100644 --- a/config/deployments/env_specific.toml +++ b/config/deployments/env_specific.toml @@ -256,6 +256,7 @@ region = "kms_region" # The AWS region used by the KMS SDK for decrypting data. [multitenancy] enabled = false +global_tenant = { schema = "public", redis_key_prefix = "" } [multitenancy.tenants] -public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = ""} \ No newline at end of file +public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} \ No newline at end of file diff --git a/config/development.toml b/config/development.toml index 9a40a99cfce6..b986a66d8459 100644 --- a/config/development.toml +++ b/config/development.toml @@ -648,6 +648,7 @@ sdk_eligible_payment_methods = "card" [multitenancy] enabled = false +global_tenant = { schema = "public", redis_key_prefix = "" } [multitenancy.tenants] -public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = ""} +public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 2cd93eade017..a9d37995728d 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -502,6 +502,7 @@ sdk_eligible_payment_methods = "card" [multitenancy] enabled = false +global_tenant = { schema = "public", redis_key_prefix = "" } [multitenancy.tenants] -public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = ""} \ No newline at end of file +public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} \ No newline at end of file diff --git a/crates/analytics/src/clickhouse.rs b/crates/analytics/src/clickhouse.rs index 64064c09c880..fd1746c4b9ee 100644 --- a/crates/analytics/src/clickhouse.rs +++ b/crates/analytics/src/clickhouse.rs @@ -35,6 +35,7 @@ pub type ClickhouseResult = error_stack::Result; #[derive(Clone, Debug)] pub struct ClickhouseClient { pub config: Arc, + pub database: String, } #[derive(Clone, Debug, serde::Deserialize)] @@ -42,7 +43,6 @@ pub struct ClickhouseConfig { username: String, password: Option, host: String, - database_name: String, } impl Default for ClickhouseConfig { @@ -51,7 +51,6 @@ impl Default for ClickhouseConfig { username: "default".to_string(), password: None, host: "http://localhost:8123".to_string(), - database_name: "default".to_string(), } } } @@ -63,7 +62,7 @@ impl ClickhouseClient { let params = CkhQuery { date_time_output_format: String::from("iso"), output_format_json_quote_64bit_integers: 0, - database: self.config.database_name.clone(), + database: self.database.clone(), }; let response = client .post(&self.config.host) diff --git a/crates/analytics/src/lib.rs b/crates/analytics/src/lib.rs index f658f6790972..5c269a4bb186 100644 --- a/crates/analytics/src/lib.rs +++ b/crates/analytics/src/lib.rs @@ -601,22 +601,30 @@ impl AnalyticsProvider { } } - pub async fn from_conf(config: &AnalyticsConfig, tenant: &str) -> Self { + pub async fn from_conf( + config: &AnalyticsConfig, + tenant: &dyn storage_impl::config::ClickHouseConfig, + ) -> Self { match config { - AnalyticsConfig::Sqlx { sqlx } => Self::Sqlx(SqlxClient::from_conf(sqlx, tenant).await), + AnalyticsConfig::Sqlx { sqlx } => { + Self::Sqlx(SqlxClient::from_conf(sqlx, tenant.get_schema()).await) + } AnalyticsConfig::Clickhouse { clickhouse } => Self::Clickhouse(ClickhouseClient { config: Arc::new(clickhouse.clone()), + database: tenant.get_clickhouse_database().to_string(), }), AnalyticsConfig::CombinedCkh { sqlx, clickhouse } => Self::CombinedCkh( - SqlxClient::from_conf(sqlx, tenant).await, + SqlxClient::from_conf(sqlx, tenant.get_schema()).await, ClickhouseClient { config: Arc::new(clickhouse.clone()), + database: tenant.get_clickhouse_database().to_string(), }, ), AnalyticsConfig::CombinedSqlx { sqlx, clickhouse } => Self::CombinedSqlx( - SqlxClient::from_conf(sqlx, tenant).await, + SqlxClient::from_conf(sqlx, tenant.get_schema()).await, ClickhouseClient { config: Arc::new(clickhouse.clone()), + database: tenant.get_clickhouse_database().to_string(), }, ), } diff --git a/crates/common_utils/src/consts.rs b/crates/common_utils/src/consts.rs index 968ef22bf976..38c8997358dd 100644 --- a/crates/common_utils/src/consts.rs +++ b/crates/common_utils/src/consts.rs @@ -87,8 +87,8 @@ pub const MAX_TTL_FOR_EXTENDED_CARD_INFO: u16 = 60 * 60 * 2; /// Default tenant to be used when multitenancy is disabled pub const DEFAULT_TENANT: &str = "public"; -/// Global tenant to be used when multitenancy is enabled -pub const GLOBAL_TENANT: &str = "global"; +/// Default tenant to be used when multitenancy is disabled +pub const TENANT_HEADER: &str = "x-tenant-id"; /// Max Length for MerchantReferenceId pub const MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH: u8 = 64; diff --git a/crates/diesel_models/src/query/user.rs b/crates/diesel_models/src/query/user.rs index dac515cb279d..2bd403a847b3 100644 --- a/crates/diesel_models/src/query/user.rs +++ b/crates/diesel_models/src/query/user.rs @@ -1,23 +1,9 @@ -use async_bb8_diesel::AsyncRunQueryDsl; use common_utils::pii; -use diesel::{ - associations::HasTable, debug_query, result::Error as DieselError, ExpressionMethods, - JoinOnDsl, QueryDsl, -}; -use error_stack::report; -use router_env::logger; +use diesel::{associations::HasTable, ExpressionMethods}; pub mod sample_data; use crate::{ - errors::{self}, - query::generics, - schema::{ - user_roles::{self, dsl as user_roles_dsl}, - users::dsl as users_dsl, - }, - user::*, - user_role::UserRole, - PgPooledConn, StorageResult, + query::generics, schema::users::dsl as users_dsl, user::*, PgPooledConn, StorageResult, }; impl UserNew { @@ -90,27 +76,6 @@ impl User { .await } - pub async fn find_joined_users_and_roles_by_merchant_id( - conn: &PgPooledConn, - mid: &str, - ) -> StorageResult> { - let query = Self::table() - .inner_join(user_roles::table.on(user_roles_dsl::user_id.eq(users_dsl::user_id))) - .filter(user_roles_dsl::merchant_id.eq(mid.to_owned())); - - logger::debug!(query = %debug_query::(&query).to_string()); - - query - .get_results_async::<(Self, UserRole)>(conn) - .await - .map_err(|err| match err { - DieselError::NotFound => { - report!(err).change_context(errors::DatabaseError::NotFound) - } - _ => report!(err).change_context(errors::DatabaseError::Others), - }) - } - pub async fn find_users_by_user_ids( conn: &PgPooledConn, user_ids: Vec, diff --git a/crates/drainer/src/settings.rs b/crates/drainer/src/settings.rs index e64b5dbd4ad5..57c17f577b11 100644 --- a/crates/drainer/src/settings.rs +++ b/crates/drainer/src/settings.rs @@ -143,6 +143,7 @@ pub struct Tenant { pub base_url: String, pub schema: String, pub redis_key_prefix: String, + pub clickhouse_database: String, } #[derive(Debug, Deserialize, Clone)] diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index e99e216463a1..f3f70cf42bd0 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -128,6 +128,7 @@ pub struct Settings { pub struct Multitenancy { pub tenants: TenantConfig, pub enabled: bool, + pub global_tenant: GlobalTenant, } impl Multitenancy { @@ -152,6 +153,7 @@ pub struct Tenant { pub base_url: String, pub schema: String, pub redis_key_prefix: String, + pub clickhouse_database: String, } impl storage_impl::config::TenantConfig for Tenant { @@ -163,6 +165,12 @@ impl storage_impl::config::TenantConfig for Tenant { } } +impl storage_impl::config::ClickHouseConfig for Tenant { + fn get_clickhouse_database(&self) -> &str { + self.clickhouse_database.as_str() + } +} + #[derive(Debug, Deserialize, Clone, Default)] pub struct GlobalTenant { pub schema: String, diff --git a/crates/router/src/middleware.rs b/crates/router/src/middleware.rs index e1601f8bbbe1..7b9919d3afaf 100644 --- a/crates/router/src/middleware.rs +++ b/crates/router/src/middleware.rs @@ -1,3 +1,4 @@ +use common_utils::consts::TENANT_HEADER; use futures::StreamExt; use router_env::{ logger, @@ -140,10 +141,17 @@ where // TODO: have a common source of truth for the list of top level fields // /crates/router_env/src/logger/storage.rs also has a list of fields called PERSISTENT_KEYS fn call(&self, req: actix_web::dev::ServiceRequest) -> Self::Future { + let tenant_id = req + .headers() + .get(TENANT_HEADER) + .and_then(|i| i.to_str().ok()) + .map(|s| s.to_owned()); let response_fut = self.service.call(req); - Box::pin( async move { + if let Some(tenant_id) = tenant_id { + router_env::tracing::Span::current().record("tenant_id", &tenant_id); + } let response = response_fut.await; router_env::tracing::Span::current().record("golden_log_line", true); response diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 92ca3c264fe6..2e7a6c4f61e8 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -5,7 +5,6 @@ use actix_web::{web, Scope}; use api_models::routing::RoutingRetrieveQuery; #[cfg(feature = "olap")] use common_enums::TransactionType; -use common_utils::consts::{DEFAULT_TENANT, GLOBAL_TENANT}; #[cfg(feature = "email")] use external_services::email::{ses::AwsSes, EmailService}; use external_services::file_storage::FileStorageInterface; @@ -257,19 +256,11 @@ impl AppState { let cache_store = get_cache_store(&conf.clone(), shut_down_signal, testable) .await .expect("Failed to create store"); - let global_tenant = if conf.multitenancy.enabled { - GLOBAL_TENANT - } else { - DEFAULT_TENANT - }; let global_store: Box = Self::get_store_interface( &storage_impl, &event_handler, &conf, - &settings::GlobalTenant { - schema: global_tenant.to_string(), - redis_key_prefix: String::default(), - }, + &conf.multitenancy.global_tenant, Arc::clone(&cache_store), testable, ) @@ -288,9 +279,7 @@ impl AppState { .get_storage_interface(); stores.insert(tenant_name.clone(), store); #[cfg(feature = "olap")] - let pool = - AnalyticsProvider::from_conf(conf.analytics.get_inner(), tenant_name.as_str()) - .await; + let pool = AnalyticsProvider::from_conf(conf.analytics.get_inner(), tenant).await; #[cfg(feature = "olap")] pools.insert(tenant_name.clone(), pool); } diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index baa824ced3f8..7408f3794894 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -19,7 +19,7 @@ use api_models::enums::{CaptureMethod, PaymentMethodType}; pub use client::{proxy_bypass_urls, ApiClient, MockApiClient, ProxyClient}; pub use common_utils::request::{ContentType, Method, Request, RequestBuilder}; use common_utils::{ - consts::X_HS_LATENCY, + consts::{DEFAULT_TENANT, TENANT_HEADER, X_HS_LATENCY}, errors::{ErrorSwitch, ReportSwitchExt}, request::RequestContent, }; @@ -941,10 +941,10 @@ where .into_iter() .collect(); let tenant_id = if !state.conf.multitenancy.enabled { - common_utils::consts::DEFAULT_TENANT.to_string() + DEFAULT_TENANT.to_string() } else { incoming_request_header - .get("x-tenant-id") + .get(TENANT_HEADER) .and_then(|value| value.to_str().ok()) .ok_or_else(|| errors::ApiErrorResponse::MissingTenantId.switch()) .map(|req_tenant_id| { diff --git a/crates/storage_impl/src/config.rs b/crates/storage_impl/src/config.rs index e371936a04dd..bb006b6a9e55 100644 --- a/crates/storage_impl/src/config.rs +++ b/crates/storage_impl/src/config.rs @@ -38,6 +38,10 @@ pub trait TenantConfig: Send + Sync { fn get_redis_key_prefix(&self) -> &str; } +pub trait ClickHouseConfig: TenantConfig + Send + Sync { + fn get_clickhouse_database(&self) -> &str; +} + #[derive(Debug, serde::Deserialize, Clone, Copy, Default)] #[serde(rename_all = "PascalCase")] pub enum QueueStrategy { diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index d008ff3231cb..ad97ef6d4041 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -328,6 +328,7 @@ keys = "user-agent" [multitenancy] enabled = false +global_tenant = { schema = "public", redis_key_prefix = "" } [multitenancy.tenants] -public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = ""} \ No newline at end of file +public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} \ No newline at end of file