All checks were successful
Run Check Script / check (pull_request) Successful in 1m15s
This includes Id, IpAddress, Url and some other heavily used types
225 lines
6.8 KiB
Rust
225 lines
6.8 KiB
Rust
pub mod k8s;
|
|
mod manager;
|
|
pub mod network_policy;
|
|
|
|
use harmony_types::id::Id;
|
|
pub use manager::*;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::str::FromStr;
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] // Assuming serde for Scores
|
|
pub struct TenantConfig {
|
|
/// This will be used as the primary unique identifier for management operations and will never
|
|
/// change for the entire lifetime of the tenant
|
|
pub id: Id,
|
|
|
|
/// A human-readable name for the tenant (e.g., "client-alpha", "project-phoenix").
|
|
pub name: String,
|
|
|
|
/// Desired resource allocations and limits for the tenant.
|
|
pub resource_limits: ResourceLimits,
|
|
|
|
/// High-level network isolation policies for the tenant.
|
|
pub network_policy: TenantNetworkPolicy,
|
|
}
|
|
|
|
impl Default for TenantConfig {
|
|
fn default() -> Self {
|
|
let id = Id::default();
|
|
Self {
|
|
name: format!("tenant_{id}"),
|
|
id,
|
|
resource_limits: ResourceLimits::default(),
|
|
network_policy: TenantNetworkPolicy {
|
|
default_inter_tenant_ingress: InterTenantIngressPolicy::DenyAll,
|
|
default_internet_egress: InternetEgressPolicy::AllowAll,
|
|
additional_allowed_cidr_ingress: vec![],
|
|
additional_allowed_cidr_egress: vec![],
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct ResourceLimits {
|
|
/// Requested/guaranteed CPU cores (e.g., 2.0).
|
|
pub cpu_request_cores: f32,
|
|
/// Maximum CPU cores the tenant can burst to (e.g., 4.0).
|
|
pub cpu_limit_cores: f32,
|
|
|
|
/// Requested/guaranteed memory in Gigabytes (e.g., 8.0).
|
|
pub memory_request_gb: f32,
|
|
/// Maximum memory in Gigabytes tenant can burst to (e.g., 16.0).
|
|
pub memory_limit_gb: f32,
|
|
|
|
/// Total persistent storage allocation in Gigabytes across all volumes.
|
|
pub storage_total_gb: f32,
|
|
}
|
|
|
|
impl Default for ResourceLimits {
|
|
fn default() -> Self {
|
|
Self {
|
|
cpu_request_cores: 4.0,
|
|
cpu_limit_cores: 4.0,
|
|
memory_request_gb: 4.0,
|
|
memory_limit_gb: 4.0,
|
|
storage_total_gb: 20.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct TenantNetworkPolicy {
|
|
/// Policy for ingress traffic originating from other tenants within the same Harmony-managed environment.
|
|
pub default_inter_tenant_ingress: InterTenantIngressPolicy,
|
|
|
|
/// Policy for egress traffic destined for the public internet.
|
|
pub default_internet_egress: InternetEgressPolicy,
|
|
|
|
pub additional_allowed_cidr_ingress: Vec<(Vec<cidr::Ipv4Cidr>, Option<PortSpec>)>,
|
|
pub additional_allowed_cidr_egress: Vec<(Vec<cidr::Ipv4Cidr>, Option<PortSpec>)>,
|
|
}
|
|
|
|
impl Default for TenantNetworkPolicy {
|
|
fn default() -> Self {
|
|
TenantNetworkPolicy {
|
|
default_inter_tenant_ingress: InterTenantIngressPolicy::DenyAll,
|
|
default_internet_egress: InternetEgressPolicy::DenyAll,
|
|
additional_allowed_cidr_ingress: vec![],
|
|
additional_allowed_cidr_egress: vec![],
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub enum InterTenantIngressPolicy {
|
|
/// Deny all traffic from other tenants by default.
|
|
DenyAll,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub enum InternetEgressPolicy {
|
|
/// Allow all outbound traffic to the internet.
|
|
AllowAll,
|
|
/// Deny all outbound traffic to the internet by default.
|
|
DenyAll,
|
|
}
|
|
|
|
/// Represents a port specification that can be either a single port, a comma-separated list of ports,
|
|
/// or a range separated by a dash.
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct PortSpec {
|
|
/// The actual representation of the ports as strings for serialization/deserialization purposes.
|
|
pub data: PortSpecData,
|
|
}
|
|
|
|
impl PortSpec {
|
|
/// TODO write short rust doc that shows what types of input are supported
|
|
fn parse_from_str(spec: &str) -> Result<PortSpec, String> {
|
|
// Check for single port
|
|
if let Ok(port) = spec.parse::<u16>() {
|
|
let spec = PortSpecData::SinglePort(port);
|
|
return Ok(Self { data: spec });
|
|
}
|
|
|
|
if let Some(range) = spec.find('-') {
|
|
let start_str = &spec[..range];
|
|
let end_str = &spec[(range + 1)..];
|
|
|
|
if let (Ok(start), Ok(end)) = (start_str.parse::<u16>(), end_str.parse::<u16>()) {
|
|
let spec = PortSpecData::PortRange(start, end);
|
|
return Ok(Self { data: spec });
|
|
}
|
|
}
|
|
|
|
let ports: Vec<&str> = spec.split(',').collect();
|
|
if !ports.is_empty() && ports.iter().all(|p| p.parse::<u16>().is_ok()) {
|
|
let maybe_ports = ports.iter().try_fold(vec![], |mut list, &p| {
|
|
if let Ok(p) = p.parse::<u16>() {
|
|
list.push(p);
|
|
return Ok(list);
|
|
}
|
|
Err(())
|
|
});
|
|
|
|
if let Ok(ports) = maybe_ports {
|
|
let spec = PortSpecData::ListOfPorts(ports);
|
|
return Ok(Self { data: spec });
|
|
}
|
|
}
|
|
|
|
Err(format!("Invalid port spec format {spec}"))
|
|
}
|
|
}
|
|
|
|
impl FromStr for PortSpec {
|
|
type Err = String;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
Self::parse_from_str(s)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub enum PortSpecData {
|
|
SinglePort(u16),
|
|
PortRange(u16, u16),
|
|
ListOfPorts(Vec<u16>),
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_single_port() {
|
|
let port_spec = "2144".parse::<PortSpec>().unwrap();
|
|
match port_spec.data {
|
|
PortSpecData::SinglePort(port) => assert_eq!(port, 2144),
|
|
_ => panic!("Expected SinglePort"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_port_range() {
|
|
let port_spec = "80-90".parse::<PortSpec>().unwrap();
|
|
match port_spec.data {
|
|
PortSpecData::PortRange(start, end) => {
|
|
assert_eq!(start, 80);
|
|
assert_eq!(end, 90);
|
|
}
|
|
_ => panic!("Expected PortRange"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_list_of_ports() {
|
|
let port_spec = "2144,3424".parse::<PortSpec>().unwrap();
|
|
match port_spec.data {
|
|
PortSpecData::ListOfPorts(ports) => {
|
|
assert_eq!(ports[0], 2144);
|
|
assert_eq!(ports[1], 3424);
|
|
}
|
|
_ => panic!("Expected ListOfPorts"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_invalid_port_spec() {
|
|
let result = "invalid".parse::<PortSpec>();
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_empty_input() {
|
|
let result = "".parse::<PortSpec>();
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_only_coma() {
|
|
let result = ",".parse::<PortSpec>();
|
|
assert!(result.is_err());
|
|
}
|
|
}
|