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:
Jagan
2024-06-03 17:57:30 +05:30
committed by GitHub
parent a1788b8da9
commit 15d6c3e846
188 changed files with 2260 additions and 1414 deletions

View File

@ -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