mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 12:15:40 +08:00
feat(multitenancy): add support for multitenancy and handle the same in router, producer, consumer, drainer and analytics (#4630)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Arun Raj M <jarnura47@gmail.com>
This commit is contained in:
@ -29,7 +29,7 @@ use database::store::PgPool;
|
||||
#[cfg(not(feature = "payouts"))]
|
||||
use hyperswitch_domain_models::{PayoutAttemptInterface, PayoutsInterface};
|
||||
pub use mock_db::MockDb;
|
||||
use redis_interface::{errors::RedisError, SaddReply};
|
||||
use redis_interface::{errors::RedisError, RedisConnectionPool, SaddReply};
|
||||
|
||||
pub use crate::database::store::DatabaseStore;
|
||||
#[cfg(not(feature = "payouts"))]
|
||||
@ -38,7 +38,7 @@ pub use crate::database::store::Store;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RouterStore<T: DatabaseStore> {
|
||||
db_store: T,
|
||||
cache_store: RedisStore,
|
||||
cache_store: Arc<RedisStore>,
|
||||
master_encryption_key: StrongSecret<Vec<u8>>,
|
||||
pub request_id: Option<String>,
|
||||
}
|
||||
@ -55,19 +55,23 @@ where
|
||||
tokio::sync::oneshot::Sender<()>,
|
||||
&'static str,
|
||||
);
|
||||
async fn new(config: Self::Config, test_transaction: bool) -> StorageResult<Self> {
|
||||
async fn new(
|
||||
config: Self::Config,
|
||||
tenant_config: &dyn config::TenantConfig,
|
||||
test_transaction: bool,
|
||||
) -> StorageResult<Self> {
|
||||
let (db_conf, cache_conf, encryption_key, cache_error_signal, inmemory_cache_stream) =
|
||||
config;
|
||||
if test_transaction {
|
||||
Self::test_store(db_conf, &cache_conf, encryption_key)
|
||||
Self::test_store(db_conf, tenant_config, &cache_conf, encryption_key)
|
||||
.await
|
||||
.attach_printable("failed to create test router store")
|
||||
} else {
|
||||
Self::from_config(
|
||||
db_conf,
|
||||
&cache_conf,
|
||||
tenant_config,
|
||||
encryption_key,
|
||||
cache_error_signal,
|
||||
Self::cache_store(&cache_conf, cache_error_signal).await?,
|
||||
inmemory_cache_stream,
|
||||
)
|
||||
.await
|
||||
@ -83,9 +87,7 @@ where
|
||||
}
|
||||
|
||||
impl<T: DatabaseStore> RedisConnInterface for RouterStore<T> {
|
||||
fn get_redis_conn(
|
||||
&self,
|
||||
) -> error_stack::Result<Arc<redis_interface::RedisConnectionPool>, RedisError> {
|
||||
fn get_redis_conn(&self) -> error_stack::Result<Arc<RedisConnectionPool>, RedisError> {
|
||||
self.cache_store.get_redis_conn()
|
||||
}
|
||||
}
|
||||
@ -93,23 +95,26 @@ impl<T: DatabaseStore> RedisConnInterface for RouterStore<T> {
|
||||
impl<T: DatabaseStore> RouterStore<T> {
|
||||
pub async fn from_config(
|
||||
db_conf: T::Config,
|
||||
cache_conf: &redis_interface::RedisSettings,
|
||||
tenant_config: &dyn config::TenantConfig,
|
||||
encryption_key: StrongSecret<Vec<u8>>,
|
||||
cache_error_signal: tokio::sync::oneshot::Sender<()>,
|
||||
cache_store: Arc<RedisStore>,
|
||||
inmemory_cache_stream: &str,
|
||||
) -> StorageResult<Self> {
|
||||
let db_store = T::new(db_conf, false).await?;
|
||||
let cache_store = RedisStore::new(cache_conf)
|
||||
.await
|
||||
.change_context(StorageError::InitializationError)
|
||||
.attach_printable("Failed to create cache store")?;
|
||||
cache_store.set_error_callback(cache_error_signal);
|
||||
let db_store = T::new(db_conf, tenant_config, false).await?;
|
||||
let redis_conn = cache_store.redis_conn.clone();
|
||||
let cache_store = Arc::new(RedisStore {
|
||||
redis_conn: Arc::new(RedisConnectionPool::clone(
|
||||
&redis_conn,
|
||||
tenant_config.get_redis_key_prefix(),
|
||||
)),
|
||||
});
|
||||
cache_store
|
||||
.redis_conn
|
||||
.subscribe(inmemory_cache_stream)
|
||||
.await
|
||||
.change_context(StorageError::InitializationError)
|
||||
.attach_printable("Failed to subscribe to inmemory cache stream")?;
|
||||
|
||||
Ok(Self {
|
||||
db_store,
|
||||
cache_store,
|
||||
@ -118,6 +123,18 @@ impl<T: DatabaseStore> RouterStore<T> {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn cache_store(
|
||||
cache_conf: &redis_interface::RedisSettings,
|
||||
cache_error_signal: tokio::sync::oneshot::Sender<()>,
|
||||
) -> StorageResult<Arc<RedisStore>> {
|
||||
let cache_store = RedisStore::new(cache_conf)
|
||||
.await
|
||||
.change_context(StorageError::InitializationError)
|
||||
.attach_printable("Failed to create cache store")?;
|
||||
cache_store.set_error_callback(cache_error_signal);
|
||||
Ok(Arc::new(cache_store))
|
||||
}
|
||||
|
||||
pub fn master_key(&self) -> &StrongSecret<Vec<u8>> {
|
||||
&self.master_encryption_key
|
||||
}
|
||||
@ -127,18 +144,19 @@ impl<T: DatabaseStore> RouterStore<T> {
|
||||
/// Will panic if `CONNECTOR_AUTH_FILE_PATH` is not set
|
||||
pub async fn test_store(
|
||||
db_conf: T::Config,
|
||||
tenant_config: &dyn config::TenantConfig,
|
||||
cache_conf: &redis_interface::RedisSettings,
|
||||
encryption_key: StrongSecret<Vec<u8>>,
|
||||
) -> StorageResult<Self> {
|
||||
// TODO: create an error enum and return proper error here
|
||||
let db_store = T::new(db_conf, true).await?;
|
||||
let db_store = T::new(db_conf, tenant_config, true).await?;
|
||||
let cache_store = RedisStore::new(cache_conf)
|
||||
.await
|
||||
.change_context(StorageError::InitializationError)
|
||||
.attach_printable("failed to create redis cache")?;
|
||||
Ok(Self {
|
||||
db_store,
|
||||
cache_store,
|
||||
cache_store: Arc::new(cache_store),
|
||||
master_encryption_key: encryption_key,
|
||||
request_id: None,
|
||||
})
|
||||
@ -162,9 +180,13 @@ where
|
||||
T: DatabaseStore,
|
||||
{
|
||||
type Config = (RouterStore<T>, String, u8, u32, Option<bool>);
|
||||
async fn new(config: Self::Config, _test_transaction: bool) -> StorageResult<Self> {
|
||||
let (router_store, drainer_stream_name, drainer_num_partitions, ttl_for_kv, soft_kill_mode) =
|
||||
config;
|
||||
async fn new(
|
||||
config: Self::Config,
|
||||
tenant_config: &dyn config::TenantConfig,
|
||||
_test_transaction: bool,
|
||||
) -> StorageResult<Self> {
|
||||
let (router_store, _, drainer_num_partitions, ttl_for_kv, soft_kill_mode) = config;
|
||||
let drainer_stream_name = format!("{}_{}", tenant_config.get_schema(), config.1);
|
||||
Ok(Self::from_store(
|
||||
router_store,
|
||||
drainer_stream_name,
|
||||
@ -182,9 +204,7 @@ where
|
||||
}
|
||||
|
||||
impl<T: DatabaseStore> RedisConnInterface for KVRouterStore<T> {
|
||||
fn get_redis_conn(
|
||||
&self,
|
||||
) -> error_stack::Result<Arc<redis_interface::RedisConnectionPool>, RedisError> {
|
||||
fn get_redis_conn(&self) -> error_stack::Result<Arc<RedisConnectionPool>, RedisError> {
|
||||
self.router_store.get_redis_conn()
|
||||
}
|
||||
}
|
||||
@ -282,7 +302,7 @@ pub trait UniqueConstraints {
|
||||
fn table_name(&self) -> &str;
|
||||
async fn check_for_constraints(
|
||||
&self,
|
||||
redis_conn: &Arc<redis_interface::RedisConnectionPool>,
|
||||
redis_conn: &Arc<RedisConnectionPool>,
|
||||
) -> CustomResult<(), RedisError> {
|
||||
let constraints = self.unique_constraints();
|
||||
let sadd_result = redis_conn
|
||||
|
||||
Reference in New Issue
Block a user