Skip to main content

oxirs_vec/multi_tenancy/
manager.rs

1//! Multi-tenant manager - main interface for multi-tenancy
2
3use crate::multi_tenancy::{
4    access_control::AccessControl,
5    billing::{BillingEngine, BillingMetrics, BillingPeriod, PricingModel},
6    isolation::{IsolationLevel, IsolationStrategy, NamespaceManager},
7    quota::{QuotaEnforcer, QuotaLimits, RateLimiter},
8    tenant::{Tenant, TenantId, TenantMetadata, TenantStatus},
9    types::{
10        MultiTenancyError, MultiTenancyResult, TenantContext, TenantOperation, TenantStatistics,
11    },
12};
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15use std::sync::{Arc, RwLock};
16
17/// Configuration for tenant
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct TenantConfig {
20    /// Tenant metadata
21    pub metadata: TenantMetadata,
22
23    /// Isolation strategy
24    pub isolation: IsolationStrategy,
25
26    /// Quota limits
27    pub quotas: QuotaLimits,
28
29    /// Pricing model
30    pub pricing: PricingModel,
31
32    /// Rate limit (requests per second)
33    pub rate_limit: Option<f64>,
34}
35
36impl TenantConfig {
37    /// Create config for free tier
38    pub fn free_tier(tenant_id: impl Into<String>, name: impl Into<String>) -> Self {
39        let tenant_id = tenant_id.into();
40        Self {
41            metadata: TenantMetadata::new(name, "free"),
42            isolation: IsolationStrategy::free_tier(),
43            quotas: QuotaLimits::free_tier(&tenant_id),
44            pricing: PricingModel::PerRequest {
45                cost_per_request: 0.001,
46            },
47            rate_limit: Some(10.0),
48        }
49    }
50
51    /// Create config for pro tier
52    pub fn pro_tier(tenant_id: impl Into<String>, name: impl Into<String>) -> Self {
53        let tenant_id = tenant_id.into();
54        Self {
55            metadata: TenantMetadata::new(name, "pro"),
56            isolation: IsolationStrategy::pro_tier(),
57            quotas: QuotaLimits::pro_tier(&tenant_id),
58            pricing: PricingModel::PerComputeUnit {
59                cost_per_unit: 0.01,
60            },
61            rate_limit: Some(100.0),
62        }
63    }
64
65    /// Create config for enterprise tier
66    pub fn enterprise_tier(tenant_id: impl Into<String>, name: impl Into<String>) -> Self {
67        let tenant_id = tenant_id.into();
68        Self {
69            metadata: TenantMetadata::new(name, "enterprise"),
70            isolation: IsolationStrategy::enterprise_tier(),
71            quotas: QuotaLimits::enterprise_tier(&tenant_id),
72            pricing: PricingModel::Subscription {
73                monthly_fee: 1000.0,
74                included_requests: 1_000_000,
75                overage_cost: 0.005,
76            },
77            rate_limit: None, // Unlimited
78        }
79    }
80}
81
82/// Configuration for multi-tenant manager
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct TenantManagerConfig {
85    /// Default isolation level
86    pub default_isolation: IsolationLevel,
87
88    /// Billing period
89    pub billing_period: BillingPeriod,
90
91    /// Enable strict quota enforcement
92    pub strict_quotas: bool,
93
94    /// Enable access control
95    pub enable_access_control: bool,
96
97    /// Enable billing/metering
98    pub enable_billing: bool,
99
100    /// Auto-suspend tenants on quota exceeded
101    pub auto_suspend_on_quota_exceeded: bool,
102}
103
104impl TenantManagerConfig {
105    /// Create default configuration
106    pub fn default_config() -> Self {
107        Self {
108            default_isolation: IsolationLevel::Namespace,
109            billing_period: BillingPeriod::Monthly,
110            strict_quotas: true,
111            enable_access_control: true,
112            enable_billing: true,
113            auto_suspend_on_quota_exceeded: false,
114        }
115    }
116
117    /// Create production configuration
118    pub fn production() -> Self {
119        Self {
120            default_isolation: IsolationLevel::SeparateIndex,
121            billing_period: BillingPeriod::Monthly,
122            strict_quotas: true,
123            enable_access_control: true,
124            enable_billing: true,
125            auto_suspend_on_quota_exceeded: true,
126        }
127    }
128}
129
130/// Multi-tenant manager
131pub struct MultiTenantManager {
132    /// Configuration
133    config: TenantManagerConfig,
134
135    /// Tenant registry
136    tenants: Arc<RwLock<HashMap<TenantId, Tenant>>>,
137
138    /// Tenant statistics
139    statistics: Arc<RwLock<HashMap<TenantId, TenantStatistics>>>,
140
141    /// Namespace manager
142    namespace_manager: Arc<NamespaceManager>,
143
144    /// Quota enforcer
145    quota_enforcer: Arc<QuotaEnforcer>,
146
147    /// Rate limiter
148    rate_limiter: Arc<RateLimiter>,
149
150    /// Access control
151    access_control: Arc<AccessControl>,
152
153    /// Billing engine
154    billing_engine: Arc<BillingEngine>,
155}
156
157impl MultiTenantManager {
158    /// Create new multi-tenant manager
159    pub fn new(config: TenantManagerConfig) -> Self {
160        let isolation_strategy = IsolationStrategy::new(config.default_isolation);
161        let namespace_manager = Arc::new(NamespaceManager::new(isolation_strategy));
162        let quota_enforcer = Arc::new(QuotaEnforcer::new());
163        let rate_limiter = Arc::new(RateLimiter::new());
164        let access_control = Arc::new(AccessControl::new());
165        let billing_engine = Arc::new(BillingEngine::new(config.billing_period));
166
167        Self {
168            config,
169            tenants: Arc::new(RwLock::new(HashMap::new())),
170            statistics: Arc::new(RwLock::new(HashMap::new())),
171            namespace_manager,
172            quota_enforcer,
173            rate_limiter,
174            access_control,
175            billing_engine,
176        }
177    }
178
179    /// Create with default configuration
180    pub fn with_defaults() -> Self {
181        Self::new(TenantManagerConfig::default_config())
182    }
183
184    /// Create tenant with configuration
185    pub fn create_tenant(
186        &self,
187        tenant_id: impl Into<String>,
188        config: TenantConfig,
189    ) -> MultiTenancyResult<()> {
190        let tenant_id = tenant_id.into();
191        let tenant = Tenant::new(tenant_id.clone(), config.metadata);
192
193        // Register namespace
194        self.namespace_manager.register_tenant(&tenant_id)?;
195
196        // Set quota limits
197        self.quota_enforcer.set_limits(config.quotas)?;
198
199        // Set rate limit
200        if let Some(rate) = config.rate_limit {
201            self.rate_limiter.set_rate(&tenant_id, rate)?;
202        }
203
204        // Set pricing model
205        self.billing_engine
206            .set_pricing(&tenant_id, config.pricing)?;
207
208        // Create default access policy
209        if self.config.enable_access_control {
210            self.access_control.create_default_policy(&tenant_id)?;
211        }
212
213        // Store tenant
214        self.tenants
215            .write()
216            .map_err(|e| MultiTenancyError::InternalError {
217                message: format!("Lock error: {}", e),
218            })?
219            .insert(tenant_id.clone(), tenant);
220
221        // Initialize statistics
222        self.statistics
223            .write()
224            .map_err(|e| MultiTenancyError::InternalError {
225                message: format!("Lock error: {}", e),
226            })?
227            .insert(tenant_id.clone(), TenantStatistics::new(tenant_id));
228
229        Ok(())
230    }
231
232    /// Get tenant by ID
233    pub fn get_tenant(&self, tenant_id: &str) -> MultiTenancyResult<Tenant> {
234        self.tenants
235            .read()
236            .map_err(|e| MultiTenancyError::InternalError {
237                message: format!("Lock error: {}", e),
238            })?
239            .get(tenant_id)
240            .cloned()
241            .ok_or_else(|| MultiTenancyError::TenantNotFound {
242                tenant_id: tenant_id.to_string(),
243            })
244    }
245
246    /// Update tenant status
247    pub fn update_tenant_status(
248        &self,
249        tenant_id: &str,
250        status: TenantStatus,
251    ) -> MultiTenancyResult<()> {
252        let mut tenants = self
253            .tenants
254            .write()
255            .map_err(|e| MultiTenancyError::InternalError {
256                message: format!("Lock error: {}", e),
257            })?;
258
259        let tenant =
260            tenants
261                .get_mut(tenant_id)
262                .ok_or_else(|| MultiTenancyError::TenantNotFound {
263                    tenant_id: tenant_id.to_string(),
264                })?;
265
266        tenant.set_status(status);
267        Ok(())
268    }
269
270    /// Delete tenant
271    pub fn delete_tenant(&self, tenant_id: &str) -> MultiTenancyResult<()> {
272        // Remove from registry
273        self.tenants
274            .write()
275            .map_err(|e| MultiTenancyError::InternalError {
276                message: format!("Lock error: {}", e),
277            })?
278            .remove(tenant_id);
279
280        // Remove namespace
281        self.namespace_manager.unregister_tenant(tenant_id)?;
282
283        // Remove statistics
284        self.statistics
285            .write()
286            .map_err(|e| MultiTenancyError::InternalError {
287                message: format!("Lock error: {}", e),
288            })?
289            .remove(tenant_id);
290
291        Ok(())
292    }
293
294    /// Check if tenant can execute operation
295    pub fn check_operation(
296        &self,
297        context: &TenantContext,
298        _operation: TenantOperation,
299        resource_delta: Option<(crate::multi_tenancy::quota::ResourceType, u64)>,
300    ) -> MultiTenancyResult<()> {
301        let tenant_id = &context.tenant_id;
302
303        // Check tenant status
304        let tenant = self.get_tenant(tenant_id)?;
305        if !tenant.is_operational() {
306            if tenant.status == TenantStatus::Suspended {
307                return Err(MultiTenancyError::TenantSuspended {
308                    tenant_id: tenant_id.clone(),
309                });
310            }
311            return Err(MultiTenancyError::InternalError {
312                message: format!("Tenant {} is not operational", tenant_id),
313            });
314        }
315
316        // Check rate limit
317        if !self.rate_limiter.allow_request(tenant_id)? {
318            return Err(MultiTenancyError::RateLimitExceeded {
319                tenant_id: tenant_id.clone(),
320            });
321        }
322
323        // Check resource quota
324        if let Some((resource_type, amount)) = resource_delta {
325            if self.config.strict_quotas
326                && !self
327                    .quota_enforcer
328                    .check_quota(tenant_id, resource_type, amount)?
329            {
330                if self.config.auto_suspend_on_quota_exceeded {
331                    self.update_tenant_status(tenant_id, TenantStatus::Suspended)?;
332                }
333                return Err(MultiTenancyError::QuotaExceeded {
334                    tenant_id: tenant_id.clone(),
335                    resource: resource_type.name(),
336                });
337            }
338        }
339
340        Ok(())
341    }
342
343    /// Execute operation with full checks
344    pub fn execute_operation<F, R>(
345        &self,
346        context: &TenantContext,
347        operation: TenantOperation,
348        func: F,
349    ) -> MultiTenancyResult<R>
350    where
351        F: FnOnce() -> MultiTenancyResult<R>,
352    {
353        // Pre-execution checks
354        self.check_operation(context, operation, None)?;
355
356        // Execute operation
357        let start = chrono::Utc::now();
358        let result = func()?;
359        let latency_ms = (chrono::Utc::now() - start).num_milliseconds() as f64;
360
361        // Post-execution: record statistics and billing
362        self.record_operation_completed(context, operation, latency_ms)?;
363
364        Ok(result)
365    }
366
367    /// Record operation completion
368    fn record_operation_completed(
369        &self,
370        context: &TenantContext,
371        operation: TenantOperation,
372        latency_ms: f64,
373    ) -> MultiTenancyResult<()> {
374        let tenant_id = &context.tenant_id;
375
376        // Update statistics
377        let mut stats = self
378            .statistics
379            .write()
380            .map_err(|e| MultiTenancyError::InternalError {
381                message: format!("Lock error: {}", e),
382            })?;
383
384        stats
385            .entry(tenant_id.clone())
386            .or_insert_with(|| TenantStatistics::new(tenant_id))
387            .record_operation(operation);
388
389        if operation == TenantOperation::VectorSearch {
390            stats
391                .get_mut(tenant_id)
392                .expect("tenant stats entry was just inserted via or_insert_with")
393                .record_query(latency_ms);
394        }
395
396        // Record billing
397        if self.config.enable_billing {
398            self.billing_engine.record_usage(tenant_id, operation, 1)?;
399        }
400
401        Ok(())
402    }
403
404    /// Get tenant statistics
405    pub fn get_statistics(&self, tenant_id: &str) -> MultiTenancyResult<TenantStatistics> {
406        self.statistics
407            .read()
408            .map_err(|e| MultiTenancyError::InternalError {
409                message: format!("Lock error: {}", e),
410            })?
411            .get(tenant_id)
412            .cloned()
413            .ok_or_else(|| MultiTenancyError::TenantNotFound {
414                tenant_id: tenant_id.to_string(),
415            })
416    }
417
418    /// Get billing metrics
419    pub fn get_billing_metrics(&self, tenant_id: &str) -> MultiTenancyResult<BillingMetrics> {
420        self.billing_engine.get_metrics(tenant_id)
421    }
422
423    /// List all tenants
424    pub fn list_tenants(&self) -> MultiTenancyResult<Vec<Tenant>> {
425        Ok(self
426            .tenants
427            .read()
428            .map_err(|e| MultiTenancyError::InternalError {
429                message: format!("Lock error: {}", e),
430            })?
431            .values()
432            .cloned()
433            .collect())
434    }
435
436    /// Get namespace manager
437    pub fn namespace_manager(&self) -> &NamespaceManager {
438        &self.namespace_manager
439    }
440
441    /// Get quota enforcer
442    pub fn quota_enforcer(&self) -> &QuotaEnforcer {
443        &self.quota_enforcer
444    }
445
446    /// Get access control
447    pub fn access_control(&self) -> &AccessControl {
448        &self.access_control
449    }
450
451    /// Get billing engine
452    pub fn billing_engine(&self) -> &BillingEngine {
453        &self.billing_engine
454    }
455}
456
457#[cfg(test)]
458mod tests {
459    use super::*;
460
461    #[test]
462    fn test_tenant_config_tiers() {
463        let free = TenantConfig::free_tier("t1", "Free Tenant");
464        assert_eq!(free.metadata.tier, "free");
465        assert!(free.rate_limit.is_some());
466
467        let pro = TenantConfig::pro_tier("t2", "Pro Tenant");
468        assert_eq!(pro.metadata.tier, "pro");
469
470        let enterprise = TenantConfig::enterprise_tier("t3", "Enterprise");
471        assert_eq!(enterprise.metadata.tier, "enterprise");
472        assert!(enterprise.rate_limit.is_none()); // Unlimited
473    }
474
475    #[test]
476    fn test_manager_creation() {
477        let manager = MultiTenantManager::with_defaults();
478        assert_eq!(manager.list_tenants().unwrap().len(), 0);
479    }
480
481    #[test]
482    fn test_create_and_get_tenant() {
483        let manager = MultiTenantManager::with_defaults();
484        let config = TenantConfig::free_tier("tenant1", "Test Tenant");
485
486        manager.create_tenant("tenant1", config).unwrap();
487
488        let tenant = manager.get_tenant("tenant1").unwrap();
489        assert_eq!(tenant.id, "tenant1");
490        assert_eq!(tenant.metadata.name, "Test Tenant");
491        assert_eq!(tenant.status, TenantStatus::Active);
492    }
493
494    #[test]
495    fn test_tenant_operations() {
496        let manager = MultiTenantManager::with_defaults();
497        let config = TenantConfig::free_tier("tenant1", "Test");
498        manager.create_tenant("tenant1", config).unwrap();
499
500        let context = TenantContext::new("tenant1");
501
502        // Should allow operation
503        manager
504            .check_operation(&context, TenantOperation::VectorSearch, None)
505            .unwrap();
506
507        // Execute operation with closure
508        let result: MultiTenancyResult<i32> =
509            manager.execute_operation(&context, TenantOperation::VectorSearch, || Ok(42));
510        assert_eq!(result.unwrap(), 42);
511
512        // Check statistics updated
513        let stats = manager.get_statistics("tenant1").unwrap();
514        assert_eq!(stats.total_queries, 1);
515    }
516
517    #[test]
518    fn test_tenant_status_changes() {
519        let manager = MultiTenantManager::with_defaults();
520        let config = TenantConfig::free_tier("tenant1", "Test");
521        manager.create_tenant("tenant1", config).unwrap();
522
523        // Suspend tenant
524        manager
525            .update_tenant_status("tenant1", TenantStatus::Suspended)
526            .unwrap();
527
528        let tenant = manager.get_tenant("tenant1").unwrap();
529        assert_eq!(tenant.status, TenantStatus::Suspended);
530
531        // Operations should fail when suspended
532        let context = TenantContext::new("tenant1");
533        assert!(manager
534            .check_operation(&context, TenantOperation::VectorSearch, None)
535            .is_err());
536    }
537
538    #[test]
539    fn test_delete_tenant() {
540        let manager = MultiTenantManager::with_defaults();
541        let config = TenantConfig::free_tier("tenant1", "Test");
542        manager.create_tenant("tenant1", config).unwrap();
543
544        assert!(manager.get_tenant("tenant1").is_ok());
545
546        manager.delete_tenant("tenant1").unwrap();
547
548        assert!(manager.get_tenant("tenant1").is_err());
549    }
550
551    #[test]
552    fn test_list_tenants() {
553        let manager = MultiTenantManager::with_defaults();
554
555        manager
556            .create_tenant("tenant1", TenantConfig::free_tier("tenant1", "T1"))
557            .unwrap();
558        manager
559            .create_tenant("tenant2", TenantConfig::pro_tier("tenant2", "T2"))
560            .unwrap();
561        manager
562            .create_tenant("tenant3", TenantConfig::enterprise_tier("tenant3", "T3"))
563            .unwrap();
564
565        let tenants = manager.list_tenants().unwrap();
566        assert_eq!(tenants.len(), 3);
567    }
568
569    #[test]
570    fn test_billing_integration() {
571        let manager = MultiTenantManager::with_defaults();
572        let config = TenantConfig::free_tier("tenant1", "Test");
573        manager.create_tenant("tenant1", config).unwrap();
574
575        let context = TenantContext::new("tenant1");
576
577        // Execute some operations
578        for _ in 0..10 {
579            let _ = manager.execute_operation(&context, TenantOperation::VectorSearch, || Ok(()));
580        }
581
582        // Check billing metrics
583        let metrics = manager.get_billing_metrics("tenant1").unwrap();
584        assert_eq!(metrics.total_requests, 10);
585        assert!(metrics.total_cost > 0.0);
586    }
587
588    #[test]
589    fn test_manager_config() {
590        let config = TenantManagerConfig::default_config();
591        assert!(config.strict_quotas);
592        assert!(config.enable_access_control);
593        assert!(config.enable_billing);
594
595        let prod_config = TenantManagerConfig::production();
596        assert_eq!(prod_config.default_isolation, IsolationLevel::SeparateIndex);
597        assert!(prod_config.auto_suspend_on_quota_exceeded);
598    }
599}