Skip to main content

mvm_core/
tenant.rs

1use serde::{Deserialize, Serialize};
2
3/// Tenant: a security, isolation, and policy boundary that may own multiple microVMs.
4/// NOT a runtime entity — tenants have no state machine.
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct TenantConfig {
7    pub tenant_id: String,
8    pub quotas: TenantQuota,
9    pub net: TenantNet,
10    pub secrets_epoch: u64,
11    pub config_version: u64,
12    /// If true, reconcile cannot auto-stop this tenant's instances.
13    pub pinned: bool,
14    /// Audit log retention in days. 0 = forever.
15    pub audit_retention_days: u32,
16    pub created_at: String,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct TenantQuota {
21    pub max_vcpus: u32,
22    pub max_mem_mib: u64,
23    pub max_running: u32,
24    pub max_warm: u32,
25    pub max_pools: u32,
26    pub max_instances_per_pool: u32,
27    pub max_disk_gib: u64,
28}
29
30impl Default for TenantQuota {
31    fn default() -> Self {
32        Self {
33            max_vcpus: 16,
34            max_mem_mib: 32768,
35            max_running: 8,
36            max_warm: 4,
37            max_pools: 4,
38            max_instances_per_pool: 16,
39            max_disk_gib: 100,
40        }
41    }
42}
43
44/// Coordinator-assigned, cluster-wide network identity for a tenant.
45/// Agents MUST consume this verbatim — never derive or hash IPs locally.
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct TenantNet {
48    /// Cluster-unique integer (0..4095), assigned by coordinator.
49    pub tenant_net_id: u16,
50    /// Coordinator-assigned CIDR, e.g. "10.240.3.0/24".
51    pub ipv4_subnet: String,
52    /// First usable IP in subnet, e.g. "10.240.3.1".
53    pub gateway_ip: String,
54    /// Bridge name derived from tenant_net_id, e.g. "br-tenant-3".
55    pub bridge_name: String,
56}
57
58impl TenantNet {
59    /// Construct TenantNet from coordinator-assigned values.
60    pub fn new(tenant_net_id: u16, ipv4_subnet: &str, gateway_ip: &str) -> Self {
61        Self {
62            tenant_net_id,
63            ipv4_subnet: ipv4_subnet.to_string(),
64            gateway_ip: gateway_ip.to_string(),
65            bridge_name: format!("br-tenant-{}", tenant_net_id),
66        }
67    }
68}
69
70/// Filesystem paths for tenant state.
71pub const TENANT_BASE: &str = "/var/lib/mvm/tenants";
72
73pub fn tenant_dir(id: &str) -> String {
74    format!("{}/{}", TENANT_BASE, id)
75}
76
77pub fn tenant_config_path(id: &str) -> String {
78    format!("{}/tenant.json", tenant_dir(id))
79}
80
81pub fn tenant_secrets_path(id: &str) -> String {
82    format!("{}/secrets.json", tenant_dir(id))
83}
84
85pub fn tenant_audit_log_path(id: &str) -> String {
86    format!("{}/audit.log", tenant_dir(id))
87}
88
89pub fn tenant_ssh_key_path(id: &str) -> String {
90    format!("{}/ssh_key", tenant_dir(id))
91}
92
93pub fn tenant_pools_dir(id: &str) -> String {
94    format!("{}/pools", tenant_dir(id))
95}