From 27f1a9dbdd0e3be9a0fdbb8034d63647af0315c9 Mon Sep 17 00:00:00 2001 From: Taha Hawa Date: Thu, 29 May 2025 20:15:38 +0000 Subject: [PATCH] feat: add more to the tenantmanager k8s impl (#46) Co-authored-by: Willem Reviewed-on: https://git.nationtech.io/NationTech/harmony/pulls/46 Co-authored-by: Taha Hawa Co-committed-by: Taha Hawa --- harmony/src/domain/topology/k8s_anywhere.rs | 58 ++++++++++- harmony/src/domain/topology/tenant/k8s.rs | 95 +++++++++++++++++++ harmony/src/domain/topology/tenant/manager.rs | 2 +- harmony/src/domain/topology/tenant/mod.rs | 11 ++- 4 files changed, 158 insertions(+), 8 deletions(-) create mode 100644 harmony/src/domain/topology/tenant/k8s.rs diff --git a/harmony/src/domain/topology/k8s_anywhere.rs b/harmony/src/domain/topology/k8s_anywhere.rs index bcd95bc..369f030 100644 --- a/harmony/src/domain/topology/k8s_anywhere.rs +++ b/harmony/src/domain/topology/k8s_anywhere.rs @@ -1,4 +1,4 @@ -use std::{process::Command, sync::Arc}; +use std::{io::Error, process::Command, sync::Arc}; use async_trait::async_trait; use inquire::Confirm; @@ -6,6 +6,7 @@ use log::{info, warn}; use tokio::sync::OnceCell; use crate::{ + executors::ExecutorError, interpret::{InterpretError, Outcome}, inventory::Inventory, maestro::Maestro, @@ -13,7 +14,13 @@ use crate::{ topology::LocalhostTopology, }; -use super::{HelmCommand, K8sclient, Topology, k8s::K8sClient}; +use super::{ + HelmCommand, K8sclient, Topology, + k8s::K8sClient, + tenant::{ + ResourceLimits, TenantConfig, TenantManager, TenantNetworkPolicy, k8s::K8sTenantManager, + }, +}; struct K8sState { client: Arc, @@ -21,6 +28,7 @@ struct K8sState { message: String, } +#[derive(Debug)] enum K8sSource { LocalK3d, Kubeconfig, @@ -28,6 +36,7 @@ enum K8sSource { pub struct K8sAnywhereTopology { k8s_state: OnceCell>, + tenant_manager: OnceCell, } #[async_trait] @@ -51,6 +60,7 @@ impl K8sAnywhereTopology { pub fn new() -> Self { Self { k8s_state: OnceCell::new(), + tenant_manager: OnceCell::new(), } } @@ -159,6 +169,15 @@ impl K8sAnywhereTopology { Ok(Some(state)) } + + fn get_k8s_tenant_manager(&self) -> Result<&K8sTenantManager, ExecutorError> { + match self.tenant_manager.get() { + Some(t) => Ok(t), + None => Err(ExecutorError::UnexpectedError( + "K8sTenantManager not available".to_string(), + )), + } + } } struct K8sAnywhereConfig { @@ -209,3 +228,38 @@ impl Topology for K8sAnywhereTopology { } impl HelmCommand for K8sAnywhereTopology {} + +#[async_trait] +impl TenantManager for K8sAnywhereTopology { + async fn provision_tenant(&self, config: &TenantConfig) -> Result<(), ExecutorError> { + self.get_k8s_tenant_manager()? + .provision_tenant(config) + .await + } + + async fn update_tenant_resource_limits( + &self, + tenant_name: &str, + new_limits: &ResourceLimits, + ) -> Result<(), ExecutorError> { + self.get_k8s_tenant_manager()? + .update_tenant_resource_limits(tenant_name, new_limits) + .await + } + + async fn update_tenant_network_policy( + &self, + tenant_name: &str, + new_policy: &TenantNetworkPolicy, + ) -> Result<(), ExecutorError> { + self.get_k8s_tenant_manager()? + .update_tenant_network_policy(tenant_name, new_policy) + .await + } + + async fn deprovision_tenant(&self, tenant_name: &str) -> Result<(), ExecutorError> { + self.get_k8s_tenant_manager()? + .deprovision_tenant(tenant_name) + .await + } +} diff --git a/harmony/src/domain/topology/tenant/k8s.rs b/harmony/src/domain/topology/tenant/k8s.rs new file mode 100644 index 0000000..88cf712 --- /dev/null +++ b/harmony/src/domain/topology/tenant/k8s.rs @@ -0,0 +1,95 @@ +use std::sync::Arc; + +use crate::{executors::ExecutorError, topology::k8s::K8sClient}; +use async_trait::async_trait; +use derive_new::new; +use k8s_openapi::api::core::v1::Namespace; +use serde_json::json; + +use super::{ResourceLimits, TenantConfig, TenantManager, TenantNetworkPolicy}; + +#[derive(new)] +pub struct K8sTenantManager { + k8s_client: Arc, +} + +#[async_trait] +impl TenantManager for K8sTenantManager { + async fn provision_tenant(&self, config: &TenantConfig) -> Result<(), ExecutorError> { + let namespace = json!( + { + "apiVersion": "v1", + "kind": "Namespace", + "metadata": { + "labels": { + "harmony.nationtech.io/tenant.id": config.id, + "harmony.nationtech.io/tenant.name": config.name, + }, + "name": config.name, + }, + } + ); + todo!("Validate that when tenant already exists (by id) that name has not changed"); + + let namespace: Namespace = serde_json::from_value(namespace).unwrap(); + + let resource_quota = json!( + { + "apiVersion": "v1", + "kind": "List", + "items": [ + { + "apiVersion": "v1", + "kind": "ResourceQuota", + "metadata": { + "name": config.name, + "labels": { + "harmony.nationtech.io/tenant.id": config.id, + "harmony.nationtech.io/tenant.name": config.name, + }, + "namespace": config.name, + }, + "spec": { + "hard": { + "limits.cpu": format!("{:.0}",config.resource_limits.cpu_limit_cores), + "limits.memory": format!("{:.3}Gi", config.resource_limits.memory_limit_gb), + "requests.cpu": format!("{:.0}",config.resource_limits.cpu_request_cores), + "requests.memory": format!("{:.3}Gi", config.resource_limits.memory_request_gb), + "requests.storage": format!("{:.3}", config.resource_limits.storage_total_gb), + "pods": "20", + "services": "10", + "configmaps": "30", + "secrets": "30", + "persistentvolumeclaims": "15", + "services.loadbalancers": "2", + "services.nodeports": "5", + + } + } + } + ] + } + + ); + } + + async fn update_tenant_resource_limits( + &self, + tenant_name: &str, + new_limits: &ResourceLimits, + ) -> Result<(), ExecutorError> { + todo!() + } + + async fn update_tenant_network_policy( + &self, + tenant_name: &str, + new_policy: &TenantNetworkPolicy, + ) -> Result<(), ExecutorError> { + todo!() + } + + async fn deprovision_tenant(&self, tenant_name: &str) -> Result<(), ExecutorError> { + todo!() + } +} diff --git a/harmony/src/domain/topology/tenant/manager.rs b/harmony/src/domain/topology/tenant/manager.rs index b1b7eb3..4166261 100644 --- a/harmony/src/domain/topology/tenant/manager.rs +++ b/harmony/src/domain/topology/tenant/manager.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use crate::executors::ExecutorError; #[async_trait] -pub trait TenantManager: Send + Sync + std::fmt::Debug { +pub trait TenantManager { /// Provisions a new tenant based on the provided configuration. /// This operation should be idempotent; if a tenant with the same `config.name` /// already exists and matches the config, it will succeed without changes. diff --git a/harmony/src/domain/topology/tenant/mod.rs b/harmony/src/domain/topology/tenant/mod.rs index 0704a34..e1e93a2 100644 --- a/harmony/src/domain/topology/tenant/mod.rs +++ b/harmony/src/domain/topology/tenant/mod.rs @@ -1,3 +1,4 @@ +pub mod k8s; mod manager; pub use manager::*; use serde::{Deserialize, Serialize}; @@ -29,17 +30,17 @@ pub struct TenantConfig { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] pub struct ResourceLimits { /// Requested/guaranteed CPU cores (e.g., 2.0). - pub cpu_request_cores: Option, + pub cpu_request_cores: f32, /// Maximum CPU cores the tenant can burst to (e.g., 4.0). - pub cpu_limit_cores: Option, + pub cpu_limit_cores: f32, /// Requested/guaranteed memory in Gigabytes (e.g., 8.0). - pub memory_request_gb: Option, + pub memory_request_gb: f32, /// Maximum memory in Gigabytes tenant can burst to (e.g., 16.0). - pub memory_limit_gb: Option, + pub memory_limit_gb: f32, /// Total persistent storage allocation in Gigabytes across all volumes. - pub storage_total_gb: Option, + pub storage_total_gb: f32, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]