rustkernel_core/security/
tenancy.rs

1//! Multi-Tenancy Support
2//!
3//! Provides tenant isolation and resource quotas.
4
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8/// Tenant identifier
9#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub struct TenantId(String);
11
12impl TenantId {
13    /// Create a new tenant ID
14    pub fn new(id: impl Into<String>) -> Self {
15        Self(id.into())
16    }
17
18    /// Get the ID as a string
19    pub fn as_str(&self) -> &str {
20        &self.0
21    }
22}
23
24impl std::fmt::Display for TenantId {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        write!(f, "{}", self.0)
27    }
28}
29
30impl From<String> for TenantId {
31    fn from(s: String) -> Self {
32        Self(s)
33    }
34}
35
36impl From<&str> for TenantId {
37    fn from(s: &str) -> Self {
38        Self(s.to_string())
39    }
40}
41
42/// Tenant configuration
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct TenantConfig {
45    /// Tenant ID
46    pub id: TenantId,
47    /// Tenant name
48    pub name: String,
49    /// Resource quotas
50    pub quotas: ResourceQuota,
51    /// Enabled features
52    pub features: Vec<String>,
53    /// Custom metadata
54    pub metadata: HashMap<String, String>,
55    /// Whether tenant is active
56    pub active: bool,
57}
58
59impl TenantConfig {
60    /// Create a new tenant config
61    pub fn new(id: impl Into<TenantId>, name: impl Into<String>) -> Self {
62        Self {
63            id: id.into(),
64            name: name.into(),
65            quotas: ResourceQuota::default(),
66            features: Vec::new(),
67            metadata: HashMap::new(),
68            active: true,
69        }
70    }
71
72    /// Set resource quotas
73    pub fn with_quotas(mut self, quotas: ResourceQuota) -> Self {
74        self.quotas = quotas;
75        self
76    }
77
78    /// Enable a feature
79    pub fn with_feature(mut self, feature: impl Into<String>) -> Self {
80        self.features.push(feature.into());
81        self
82    }
83
84    /// Add metadata
85    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
86        self.metadata.insert(key.into(), value.into());
87        self
88    }
89
90    /// Check if a feature is enabled
91    pub fn has_feature(&self, feature: &str) -> bool {
92        self.features.iter().any(|f| f == feature)
93    }
94}
95
96/// Tenant information
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct Tenant {
99    /// Tenant configuration
100    pub config: TenantConfig,
101    /// Current resource usage
102    pub usage: ResourceUsage,
103}
104
105impl Tenant {
106    /// Create a new tenant
107    pub fn new(config: TenantConfig) -> Self {
108        Self {
109            config,
110            usage: ResourceUsage::default(),
111        }
112    }
113
114    /// Get tenant ID
115    pub fn id(&self) -> &TenantId {
116        &self.config.id
117    }
118
119    /// Check if a resource quota is exceeded
120    pub fn is_quota_exceeded(&self, resource: &str) -> bool {
121        match resource {
122            "kernels" => {
123                self.config.quotas.max_kernels > 0
124                    && self.usage.active_kernels >= self.config.quotas.max_kernels
125            }
126            "messages" => {
127                self.config.quotas.max_messages_per_second > 0
128                    && self.usage.messages_per_second >= self.config.quotas.max_messages_per_second
129            }
130            "memory" => {
131                self.config.quotas.max_memory_bytes > 0
132                    && self.usage.memory_bytes >= self.config.quotas.max_memory_bytes
133            }
134            _ => false,
135        }
136    }
137}
138
139/// Resource quotas for a tenant
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct ResourceQuota {
142    /// Maximum active kernel instances
143    pub max_kernels: u64,
144    /// Maximum messages per second
145    pub max_messages_per_second: u64,
146    /// Maximum GPU memory in bytes
147    pub max_memory_bytes: u64,
148    /// Maximum CPU cores
149    pub max_cpu_cores: u64,
150    /// Maximum concurrent requests
151    pub max_concurrent_requests: u64,
152    /// Maximum storage in bytes
153    pub max_storage_bytes: u64,
154}
155
156impl Default for ResourceQuota {
157    fn default() -> Self {
158        Self {
159            max_kernels: 100,
160            max_messages_per_second: 10_000,
161            max_memory_bytes: 1024 * 1024 * 1024, // 1 GB
162            max_cpu_cores: 4,
163            max_concurrent_requests: 100,
164            max_storage_bytes: 10 * 1024 * 1024 * 1024, // 10 GB
165        }
166    }
167}
168
169impl ResourceQuota {
170    /// Create unlimited quotas
171    pub fn unlimited() -> Self {
172        Self {
173            max_kernels: 0,
174            max_messages_per_second: 0,
175            max_memory_bytes: 0,
176            max_cpu_cores: 0,
177            max_concurrent_requests: 0,
178            max_storage_bytes: 0,
179        }
180    }
181
182    /// Set max kernels
183    pub fn with_max_kernels(mut self, max: u64) -> Self {
184        self.max_kernels = max;
185        self
186    }
187
188    /// Set max messages per second
189    pub fn with_max_messages(mut self, max: u64) -> Self {
190        self.max_messages_per_second = max;
191        self
192    }
193
194    /// Set max memory
195    pub fn with_max_memory(mut self, bytes: u64) -> Self {
196        self.max_memory_bytes = bytes;
197        self
198    }
199}
200
201/// Current resource usage
202#[derive(Debug, Clone, Default, Serialize, Deserialize)]
203pub struct ResourceUsage {
204    /// Active kernel instances
205    pub active_kernels: u64,
206    /// Messages per second (current rate)
207    pub messages_per_second: u64,
208    /// GPU memory usage in bytes
209    pub memory_bytes: u64,
210    /// CPU cores in use
211    pub cpu_cores: f64,
212    /// Concurrent requests
213    pub concurrent_requests: u64,
214    /// Storage used in bytes
215    pub storage_bytes: u64,
216}
217
218impl ResourceUsage {
219    /// Record kernel activation
220    pub fn record_kernel_activated(&mut self) {
221        self.active_kernels += 1;
222    }
223
224    /// Record kernel deactivation
225    pub fn record_kernel_deactivated(&mut self) {
226        self.active_kernels = self.active_kernels.saturating_sub(1);
227    }
228
229    /// Update memory usage
230    pub fn set_memory_usage(&mut self, bytes: u64) {
231        self.memory_bytes = bytes;
232    }
233
234    /// Update message rate
235    pub fn set_message_rate(&mut self, rate: u64) {
236        self.messages_per_second = rate;
237    }
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243
244    #[test]
245    fn test_tenant_id() {
246        let id = TenantId::new("tenant-123");
247        assert_eq!(id.as_str(), "tenant-123");
248        assert_eq!(format!("{}", id), "tenant-123");
249    }
250
251    #[test]
252    fn test_tenant_config() {
253        let config = TenantConfig::new("tenant-123", "Test Tenant")
254            .with_feature("gpu-kernels")
255            .with_metadata("plan", "enterprise");
256
257        assert_eq!(config.id.as_str(), "tenant-123");
258        assert!(config.has_feature("gpu-kernels"));
259        assert!(!config.has_feature("unknown-feature"));
260    }
261
262    #[test]
263    fn test_resource_quotas() {
264        let quotas = ResourceQuota::default()
265            .with_max_kernels(50)
266            .with_max_memory(2 * 1024 * 1024 * 1024);
267
268        assert_eq!(quotas.max_kernels, 50);
269        assert_eq!(quotas.max_memory_bytes, 2 * 1024 * 1024 * 1024);
270    }
271
272    #[test]
273    fn test_quota_exceeded() {
274        let mut tenant = Tenant::new(TenantConfig::new("test", "Test"));
275        tenant.config.quotas.max_kernels = 10;
276        tenant.usage.active_kernels = 10;
277
278        assert!(tenant.is_quota_exceeded("kernels"));
279        assert!(!tenant.is_quota_exceeded("memory"));
280    }
281}