rustkernel_core/security/
tenancy.rs1use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub struct TenantId(String);
11
12impl TenantId {
13 pub fn new(id: impl Into<String>) -> Self {
15 Self(id.into())
16 }
17
18 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#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct TenantConfig {
45 pub id: TenantId,
47 pub name: String,
49 pub quotas: ResourceQuota,
51 pub features: Vec<String>,
53 pub metadata: HashMap<String, String>,
55 pub active: bool,
57}
58
59impl TenantConfig {
60 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 pub fn with_quotas(mut self, quotas: ResourceQuota) -> Self {
74 self.quotas = quotas;
75 self
76 }
77
78 pub fn with_feature(mut self, feature: impl Into<String>) -> Self {
80 self.features.push(feature.into());
81 self
82 }
83
84 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 pub fn has_feature(&self, feature: &str) -> bool {
92 self.features.iter().any(|f| f == feature)
93 }
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct Tenant {
99 pub config: TenantConfig,
101 pub usage: ResourceUsage,
103}
104
105impl Tenant {
106 pub fn new(config: TenantConfig) -> Self {
108 Self {
109 config,
110 usage: ResourceUsage::default(),
111 }
112 }
113
114 pub fn id(&self) -> &TenantId {
116 &self.config.id
117 }
118
119 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#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct ResourceQuota {
142 pub max_kernels: u64,
144 pub max_messages_per_second: u64,
146 pub max_memory_bytes: u64,
148 pub max_cpu_cores: u64,
150 pub max_concurrent_requests: u64,
152 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, max_cpu_cores: 4,
163 max_concurrent_requests: 100,
164 max_storage_bytes: 10 * 1024 * 1024 * 1024, }
166 }
167}
168
169impl ResourceQuota {
170 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 pub fn with_max_kernels(mut self, max: u64) -> Self {
184 self.max_kernels = max;
185 self
186 }
187
188 pub fn with_max_messages(mut self, max: u64) -> Self {
190 self.max_messages_per_second = max;
191 self
192 }
193
194 pub fn with_max_memory(mut self, bytes: u64) -> Self {
196 self.max_memory_bytes = bytes;
197 self
198 }
199}
200
201#[derive(Debug, Clone, Default, Serialize, Deserialize)]
203pub struct ResourceUsage {
204 pub active_kernels: u64,
206 pub messages_per_second: u64,
208 pub memory_bytes: u64,
210 pub cpu_cores: f64,
212 pub concurrent_requests: u64,
214 pub storage_bytes: u64,
216}
217
218impl ResourceUsage {
219 pub fn record_kernel_activated(&mut self) {
221 self.active_kernels += 1;
222 }
223
224 pub fn record_kernel_deactivated(&mut self) {
226 self.active_kernels = self.active_kernels.saturating_sub(1);
227 }
228
229 pub fn set_memory_usage(&mut self, bytes: u64) {
231 self.memory_bytes = bytes;
232 }
233
234 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}