Skip to main content

oxirs_vec/multi_tenancy/
isolation.rs

1//! Data isolation strategies for multi-tenancy
2
3use crate::multi_tenancy::types::{MultiTenancyError, MultiTenancyResult};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::sync::{Arc, RwLock};
7
8/// Level of tenant isolation
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum IsolationLevel {
11    /// Shared index with namespace prefixes (lowest isolation, highest efficiency)
12    Namespace,
13
14    /// Separate indices per tenant (medium isolation)
15    SeparateIndex,
16
17    /// Separate databases/instances per tenant (highest isolation, highest cost)
18    Dedicated,
19}
20
21impl IsolationLevel {
22    /// Get isolation strength (0-10, higher = stronger)
23    pub fn strength(&self) -> u8 {
24        match self {
25            Self::Namespace => 3,
26            Self::SeparateIndex => 7,
27            Self::Dedicated => 10,
28        }
29    }
30
31    /// Get performance efficiency (0-10, higher = more efficient)
32    pub fn efficiency(&self) -> u8 {
33        match self {
34            Self::Namespace => 10,
35            Self::SeparateIndex => 6,
36            Self::Dedicated => 3,
37        }
38    }
39
40    /// Recommended for tenant tier
41    pub fn for_tier(tier: &str) -> Self {
42        match tier {
43            "free" | "trial" => Self::Namespace,
44            "pro" | "business" => Self::SeparateIndex,
45            "enterprise" | "dedicated" => Self::Dedicated,
46            _ => Self::Namespace,
47        }
48    }
49}
50
51/// Isolation strategy configuration
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct IsolationStrategy {
54    /// Base isolation level
55    pub level: IsolationLevel,
56
57    /// Whether to encrypt tenant data at rest
58    pub encryption_at_rest: bool,
59
60    /// Whether to encrypt tenant data in transit
61    pub encryption_in_transit: bool,
62
63    /// Custom namespace separator
64    pub namespace_separator: String,
65
66    /// Maximum namespace depth
67    pub max_namespace_depth: usize,
68}
69
70impl IsolationStrategy {
71    /// Create new isolation strategy
72    pub fn new(level: IsolationLevel) -> Self {
73        Self {
74            level,
75            encryption_at_rest: false,
76            encryption_in_transit: true,
77            namespace_separator: ":".to_string(),
78            max_namespace_depth: 5,
79        }
80    }
81
82    /// Create strategy for free tier
83    pub fn free_tier() -> Self {
84        Self::new(IsolationLevel::Namespace)
85    }
86
87    /// Create strategy for pro tier
88    pub fn pro_tier() -> Self {
89        let mut strategy = Self::new(IsolationLevel::SeparateIndex);
90        strategy.encryption_at_rest = true;
91        strategy
92    }
93
94    /// Create strategy for enterprise tier
95    pub fn enterprise_tier() -> Self {
96        let mut strategy = Self::new(IsolationLevel::Dedicated);
97        strategy.encryption_at_rest = true;
98        strategy
99    }
100
101    /// Enable encryption
102    pub fn with_encryption(mut self, at_rest: bool, in_transit: bool) -> Self {
103        self.encryption_at_rest = at_rest;
104        self.encryption_in_transit = in_transit;
105        self
106    }
107
108    /// Set custom namespace separator
109    pub fn with_separator(mut self, separator: impl Into<String>) -> Self {
110        self.namespace_separator = separator.into();
111        self
112    }
113}
114
115/// Namespace manager for tenant data isolation
116pub struct NamespaceManager {
117    /// Tenant namespaces
118    namespaces: Arc<RwLock<HashMap<String, Namespace>>>,
119
120    /// Isolation strategy
121    strategy: IsolationStrategy,
122}
123
124/// Namespace for tenant data
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct Namespace {
127    /// Tenant identifier
128    pub tenant_id: String,
129
130    /// Namespace prefix
131    pub prefix: String,
132
133    /// Sub-namespaces
134    pub sub_namespaces: Vec<String>,
135
136    /// Metadata
137    pub metadata: HashMap<String, String>,
138}
139
140impl Namespace {
141    /// Create new namespace
142    pub fn new(tenant_id: impl Into<String>, prefix: impl Into<String>) -> Self {
143        Self {
144            tenant_id: tenant_id.into(),
145            prefix: prefix.into(),
146            sub_namespaces: Vec::new(),
147            metadata: HashMap::new(),
148        }
149    }
150
151    /// Create sub-namespace
152    pub fn create_sub_namespace(&mut self, name: impl Into<String>, separator: &str) -> String {
153        let sub = format!("{}{}{}", self.prefix, separator, name.into());
154        self.sub_namespaces.push(sub.clone());
155        sub
156    }
157
158    /// Get fully qualified key
159    pub fn qualify_key(&self, key: &str, separator: &str) -> String {
160        format!("{}{}{}", self.prefix, separator, key)
161    }
162
163    /// Check if key belongs to this namespace
164    pub fn owns_key(&self, key: &str) -> bool {
165        key.starts_with(&self.prefix)
166    }
167}
168
169impl NamespaceManager {
170    /// Create new namespace manager
171    pub fn new(strategy: IsolationStrategy) -> Self {
172        Self {
173            namespaces: Arc::new(RwLock::new(HashMap::new())),
174            strategy,
175        }
176    }
177
178    /// Register namespace for tenant
179    pub fn register_tenant(&self, tenant_id: impl Into<String>) -> MultiTenancyResult<String> {
180        let tenant_id = tenant_id.into();
181        let prefix = self.generate_namespace_prefix(&tenant_id);
182
183        let namespace = Namespace::new(tenant_id.clone(), prefix.clone());
184
185        let mut namespaces =
186            self.namespaces
187                .write()
188                .map_err(|e| MultiTenancyError::InternalError {
189                    message: format!("Lock error: {}", e),
190                })?;
191
192        if namespaces.contains_key(&tenant_id) {
193            return Err(MultiTenancyError::TenantAlreadyExists {
194                tenant_id: tenant_id.clone(),
195            });
196        }
197
198        namespaces.insert(tenant_id, namespace);
199
200        Ok(prefix)
201    }
202
203    /// Unregister tenant namespace
204    pub fn unregister_tenant(&self, tenant_id: &str) -> MultiTenancyResult<()> {
205        let mut namespaces =
206            self.namespaces
207                .write()
208                .map_err(|e| MultiTenancyError::InternalError {
209                    message: format!("Lock error: {}", e),
210                })?;
211
212        namespaces
213            .remove(tenant_id)
214            .ok_or_else(|| MultiTenancyError::TenantNotFound {
215                tenant_id: tenant_id.to_string(),
216            })?;
217
218        Ok(())
219    }
220
221    /// Get namespace prefix for tenant
222    pub fn get_prefix(&self, tenant_id: &str) -> MultiTenancyResult<String> {
223        let namespaces = self
224            .namespaces
225            .read()
226            .map_err(|e| MultiTenancyError::InternalError {
227                message: format!("Lock error: {}", e),
228            })?;
229
230        namespaces
231            .get(tenant_id)
232            .map(|ns| ns.prefix.clone())
233            .ok_or_else(|| MultiTenancyError::TenantNotFound {
234                tenant_id: tenant_id.to_string(),
235            })
236    }
237
238    /// Qualify key with tenant namespace
239    pub fn qualify_key(&self, tenant_id: &str, key: &str) -> MultiTenancyResult<String> {
240        let namespaces = self
241            .namespaces
242            .read()
243            .map_err(|e| MultiTenancyError::InternalError {
244                message: format!("Lock error: {}", e),
245            })?;
246
247        let namespace =
248            namespaces
249                .get(tenant_id)
250                .ok_or_else(|| MultiTenancyError::TenantNotFound {
251                    tenant_id: tenant_id.to_string(),
252                })?;
253
254        Ok(namespace.qualify_key(key, &self.strategy.namespace_separator))
255    }
256
257    /// Extract tenant ID from namespaced key
258    pub fn extract_tenant_id(&self, namespaced_key: &str) -> MultiTenancyResult<String> {
259        let namespaces = self
260            .namespaces
261            .read()
262            .map_err(|e| MultiTenancyError::InternalError {
263                message: format!("Lock error: {}", e),
264            })?;
265
266        for (tenant_id, namespace) in namespaces.iter() {
267            if namespace.owns_key(namespaced_key) {
268                return Ok(tenant_id.clone());
269            }
270        }
271
272        Err(MultiTenancyError::IsolationViolation {
273            message: format!("No tenant owns key: {}", namespaced_key),
274        })
275    }
276
277    /// Validate that a key belongs to a tenant
278    pub fn validate_access(&self, tenant_id: &str, key: &str) -> MultiTenancyResult<bool> {
279        let namespaces = self
280            .namespaces
281            .read()
282            .map_err(|e| MultiTenancyError::InternalError {
283                message: format!("Lock error: {}", e),
284            })?;
285
286        let namespace =
287            namespaces
288                .get(tenant_id)
289                .ok_or_else(|| MultiTenancyError::TenantNotFound {
290                    tenant_id: tenant_id.to_string(),
291                })?;
292
293        Ok(namespace.owns_key(key))
294    }
295
296    /// Create sub-namespace for tenant
297    pub fn create_sub_namespace(
298        &self,
299        tenant_id: &str,
300        name: impl Into<String>,
301    ) -> MultiTenancyResult<String> {
302        let mut namespaces =
303            self.namespaces
304                .write()
305                .map_err(|e| MultiTenancyError::InternalError {
306                    message: format!("Lock error: {}", e),
307                })?;
308
309        let namespace =
310            namespaces
311                .get_mut(tenant_id)
312                .ok_or_else(|| MultiTenancyError::TenantNotFound {
313                    tenant_id: tenant_id.to_string(),
314                })?;
315
316        if namespace.sub_namespaces.len() >= self.strategy.max_namespace_depth {
317            return Err(MultiTenancyError::InvalidConfiguration {
318                message: "Maximum namespace depth exceeded".to_string(),
319            });
320        }
321
322        Ok(namespace.create_sub_namespace(name, &self.strategy.namespace_separator))
323    }
324
325    /// Get all namespaces
326    pub fn list_namespaces(&self) -> MultiTenancyResult<Vec<String>> {
327        let namespaces = self
328            .namespaces
329            .read()
330            .map_err(|e| MultiTenancyError::InternalError {
331                message: format!("Lock error: {}", e),
332            })?;
333
334        Ok(namespaces.keys().cloned().collect())
335    }
336
337    /// Generate namespace prefix from tenant ID
338    fn generate_namespace_prefix(&self, tenant_id: &str) -> String {
339        // Sanitize tenant ID for use as namespace
340        let sanitized: String = tenant_id
341            .chars()
342            .map(|c| match c {
343                '-' | '.' | '/' => '_',
344                c => c,
345            })
346            .collect();
347
348        format!("tenant_{}", sanitized)
349    }
350
351    /// Get isolation strategy
352    pub fn strategy(&self) -> &IsolationStrategy {
353        &self.strategy
354    }
355}
356
357#[cfg(test)]
358mod tests {
359    type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
360    use super::*;
361
362    #[test]
363    fn test_isolation_levels() {
364        assert_eq!(IsolationLevel::Namespace.strength(), 3);
365        assert_eq!(IsolationLevel::SeparateIndex.strength(), 7);
366        assert_eq!(IsolationLevel::Dedicated.strength(), 10);
367
368        assert_eq!(IsolationLevel::Namespace.efficiency(), 10);
369        assert_eq!(IsolationLevel::Dedicated.efficiency(), 3);
370    }
371
372    #[test]
373    fn test_isolation_level_for_tier() {
374        assert_eq!(IsolationLevel::for_tier("free"), IsolationLevel::Namespace);
375        assert_eq!(
376            IsolationLevel::for_tier("pro"),
377            IsolationLevel::SeparateIndex
378        );
379        assert_eq!(
380            IsolationLevel::for_tier("enterprise"),
381            IsolationLevel::Dedicated
382        );
383    }
384
385    #[test]
386    fn test_isolation_strategy() {
387        let strategy = IsolationStrategy::free_tier();
388        assert_eq!(strategy.level, IsolationLevel::Namespace);
389        assert!(!strategy.encryption_at_rest);
390
391        let strategy = IsolationStrategy::pro_tier();
392        assert_eq!(strategy.level, IsolationLevel::SeparateIndex);
393        assert!(strategy.encryption_at_rest);
394
395        let strategy = IsolationStrategy::enterprise_tier();
396        assert_eq!(strategy.level, IsolationLevel::Dedicated);
397        assert!(strategy.encryption_at_rest);
398    }
399
400    #[test]
401    fn test_namespace_creation() {
402        let ns = Namespace::new("tenant1", "tenant_tenant1");
403        assert_eq!(ns.tenant_id, "tenant1");
404        assert_eq!(ns.prefix, "tenant_tenant1");
405        assert!(ns.sub_namespaces.is_empty());
406    }
407
408    #[test]
409    fn test_namespace_qualification() {
410        let ns = Namespace::new("tenant1", "tenant_tenant1");
411        let qualified = ns.qualify_key("vector123", ":");
412        assert_eq!(qualified, "tenant_tenant1:vector123");
413        assert!(ns.owns_key(&qualified));
414        assert!(!ns.owns_key("other_tenant:vector123"));
415    }
416
417    #[test]
418    fn test_namespace_manager() -> Result<()> {
419        let strategy = IsolationStrategy::new(IsolationLevel::Namespace);
420        let manager = NamespaceManager::new(strategy);
421
422        // Register tenant
423        let prefix = manager.register_tenant("tenant1")?;
424        assert!(prefix.contains("tenant_tenant1"));
425
426        // Get prefix
427        let retrieved_prefix = manager.get_prefix("tenant1")?;
428        assert_eq!(prefix, retrieved_prefix);
429
430        // Qualify key
431        let qualified = manager.qualify_key("tenant1", "vector123")?;
432        assert!(qualified.starts_with(&prefix));
433        assert!(qualified.contains("vector123"));
434
435        // Validate access
436        let __val = manager.validate_access("tenant1", &qualified)?;
437        assert!(__val);
438        assert!(!manager.validate_access("tenant1", "other_tenant:key")?);
439
440        // Extract tenant ID
441        let extracted = manager.extract_tenant_id(&qualified)?;
442        assert_eq!(extracted, "tenant1");
443
444        // Unregister tenant
445        manager.unregister_tenant("tenant1")?;
446        assert!(manager.get_prefix("tenant1").is_err());
447        Ok(())
448    }
449
450    #[test]
451    fn test_sub_namespaces() -> Result<()> {
452        let strategy = IsolationStrategy::new(IsolationLevel::Namespace);
453        let manager = NamespaceManager::new(strategy);
454
455        manager.register_tenant("tenant1")?;
456
457        // Create sub-namespace
458        let sub = manager.create_sub_namespace("tenant1", "vectors")?;
459        assert!(sub.contains("tenant_tenant1"));
460        assert!(sub.contains("vectors"));
461
462        // Create another sub-namespace
463        let sub2 = manager.create_sub_namespace("tenant1", "embeddings")?;
464        assert!(sub2.contains("embeddings"));
465        assert_ne!(sub, sub2);
466        Ok(())
467    }
468
469    #[test]
470    fn test_namespace_manager_errors() -> Result<()> {
471        let strategy = IsolationStrategy::new(IsolationLevel::Namespace);
472        let manager = NamespaceManager::new(strategy);
473
474        // Getting prefix for non-existent tenant should fail
475        assert!(manager.get_prefix("nonexistent").is_err());
476
477        // Qualifying key for non-existent tenant should fail
478        assert!(manager.qualify_key("nonexistent", "key").is_err());
479
480        // Registering same tenant twice should fail
481        manager.register_tenant("tenant1")?;
482        assert!(manager.register_tenant("tenant1").is_err());
483
484        // Unregistering non-existent tenant should fail
485        assert!(manager.unregister_tenant("nonexistent").is_err());
486        Ok(())
487    }
488
489    #[test]
490    fn test_list_namespaces() -> Result<()> {
491        let strategy = IsolationStrategy::new(IsolationLevel::Namespace);
492        let manager = NamespaceManager::new(strategy);
493
494        assert_eq!(manager.list_namespaces().expect("test value").len(), 0);
495
496        manager.register_tenant("tenant1")?;
497        manager.register_tenant("tenant2")?;
498
499        let namespaces = manager.list_namespaces()?;
500        assert_eq!(namespaces.len(), 2);
501        assert!(namespaces.contains(&"tenant1".to_string()));
502        assert!(namespaces.contains(&"tenant2".to_string()));
503        Ok(())
504    }
505}