diff --git a/harmony/src/domain/topology/tenant/k8s.rs b/harmony/src/domain/topology/tenant/k8s.rs index 5feac85..ed51d96 100644 --- a/harmony/src/domain/topology/tenant/k8s.rs +++ b/harmony/src/domain/topology/tenant/k8s.rs @@ -1,9 +1,17 @@ use std::sync::Arc; -use crate::{executors::ExecutorError, topology::k8s::K8sClient}; +use crate::{data::Id, executors::ExecutorError, topology::k8s::K8sClient}; use async_trait::async_trait; use derive_new::new; -use k8s_openapi::api::core::v1::Namespace; +use k8s_openapi::{ + NamespaceResourceScope, + api::{ + core::v1::{Namespace, ResourceQuota}, + networking::v1::NetworkPolicy, + }, +}; +use kube::Resource; +use serde::de::DeserializeOwned; use serde_json::json; use super::{ResourceLimits, TenantConfig, TenantManager, TenantNetworkPolicy}; @@ -13,9 +21,29 @@ pub struct K8sTenantManager { k8s_client: Arc, } -#[async_trait] -impl TenantManager for K8sTenantManager { - async fn provision_tenant(&self, config: &TenantConfig) -> Result<(), ExecutorError> { +impl K8sTenantManager { + fn get_namespace_name(&self, config: &TenantConfig) -> String { + config.name.clone() + } + + fn ensure_constraints(&self, namespace: &Namespace) -> Result<(), ExecutorError> { + todo!("Validate that when tenant already exists (by id) that name has not changed"); + todo!("Make sure other Tenant constraints are respected by this k8s implementation"); + } + + async fn apply_resource< + K: Resource + std::fmt::Debug + Sync + DeserializeOwned + Default + serde::Serialize + Clone, + >( + &self, + resource: K, + ) -> Result + where + ::DynamicType: Default, + { + todo!("Apply tenant labels on resource and apply resource with k8s client properly") + } + + fn build_namespace(&self, config: &TenantConfig) -> Result { let namespace = json!( { "apiVersion": "v1", @@ -25,14 +53,19 @@ impl TenantManager for K8sTenantManager { "harmony.nationtech.io/tenant.id": config.id, "harmony.nationtech.io/tenant.name": config.name, }, - "name": config.name, + "name": self.get_namespace_name(config), }, } ); - todo!("Validate that when tenant already exists (by id) that name has not changed"); - - let namespace: Namespace = serde_json::from_value(namespace).unwrap(); + serde_json::from_value(namespace).map_err(|e| { + ExecutorError::ConfigurationError(format!( + "Could not build TenantManager Namespace. {}", + e + )) + }) + } + fn build_resource_quota(&self, config: &TenantConfig) -> Result { let resource_quota = json!( { "apiVersion": "v1", @@ -47,7 +80,7 @@ impl TenantManager for K8sTenantManager { "harmony.nationtech.io/tenant.id": config.id, "harmony.nationtech.io/tenant.name": config.name, }, - "namespace": config.name, + "namespace": self.get_namespace_name(config), }, "spec": { "hard": { @@ -71,7 +104,15 @@ impl TenantManager for K8sTenantManager { } ); + serde_json::from_value(resource_quota).map_err(|e| { + ExecutorError::ConfigurationError(format!( + "Could not build TenantManager ResourceQuota. {}", + e + )) + }) + } + fn build_network_policy(&self, config: &TenantConfig) -> Result { let network_policy = json!({ "apiVersion": "networking.k8s.io/v1", "kind": "NetworkPolicy", @@ -80,17 +121,87 @@ impl TenantManager for K8sTenantManager { }, "spec": { "podSelector": {}, - "egress": [], - "ingress": [], + "egress": [ + { "to": [ {"podSelector": {}}]}, + { "to": + [ + { + "podSelector": {}, + "namespaceSelector": { + "matchLabels": { + "kubernetes.io/metadata.name":"openshift-dns" + } + } + }, + ] + }, + { "to": [ + { + "ipBlock": { + + "cidr": "0.0.0.0/0", + // See https://en.wikipedia.org/wiki/Reserved_IP_addresses + "except": [ + "10.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/16", + "192.0.0.0/24", + "192.0.2.0/24", + "192.88.99.0/24", + "192.18.0.0/15", + "198.51.100.0/24", + "169.254.0.0/16", + "203.0.113.0/24", + "127.0.0.0/8", + + // Not sure we should block this one as it is + // used for multicast. But better block more than less. + "224.0.0.0/4", + "240.0.0.0/4", + "100.64.0.0/10", + "233.252.0.0/24", + "0.0.0.0/8", + ], + } + } + ] + }, + ], + "ingress": [ + { "from": [ {"podSelector": {}}]} + ], "policyTypes": [ + "Ingress", "Egress", ] } }); + + serde_json::from_value(network_policy).map_err(|e| { + ExecutorError::ConfigurationError(format!( + "Could not build TenantManager NetworkPolicy. {}", + e + )) + }) + } +} + +#[async_trait] +impl TenantManager for K8sTenantManager { + async fn provision_tenant(&self, config: &TenantConfig) -> Result<(), ExecutorError> { + let namespace = self.build_namespace(config)?; + let resource_quota = self.build_resource_quota(config)?; + let network_policy = self.build_network_policy(config)?; + + self.ensure_constraints(&namespace)?; + self.apply_resource(namespace).await?; + self.apply_resource(resource_quota).await?; + self.apply_resource(network_policy).await?; + todo!(); } async fn update_tenant_resource_limits( &self, - tenant_name: &str, + tenant_id: &Id, new_limits: &ResourceLimits, ) -> Result<(), ExecutorError> { todo!() @@ -98,13 +209,13 @@ impl TenantManager for K8sTenantManager { async fn update_tenant_network_policy( &self, - tenant_name: &str, + tenant_id: &Id, new_policy: &TenantNetworkPolicy, ) -> Result<(), ExecutorError> { todo!() } - async fn deprovision_tenant(&self, tenant_name: &str) -> Result<(), ExecutorError> { + async fn deprovision_tenant(&self, tenant_id: &Id) -> Result<(), ExecutorError> { todo!() } } diff --git a/harmony/src/domain/topology/tenant/manager.rs b/harmony/src/domain/topology/tenant/manager.rs index 4166261..df042c8 100644 --- a/harmony/src/domain/topology/tenant/manager.rs +++ b/harmony/src/domain/topology/tenant/manager.rs @@ -16,31 +16,20 @@ pub trait TenantManager { async fn provision_tenant(&self, config: &TenantConfig) -> Result<(), ExecutorError>; /// Updates the resource limits for an existing tenant. - /// - /// # Arguments - /// * `tenant_name`: The logical name of the tenant to update. - /// * `new_limits`: The new set of resource limits to apply. async fn update_tenant_resource_limits( &self, - tenant_name: &str, + tenant_id: &Id, new_limits: &ResourceLimits, ) -> Result<(), ExecutorError>; /// Updates the high-level network isolation policy for an existing tenant. - /// - /// # Arguments - /// * `tenant_name`: The logical name of the tenant to update. - /// * `new_policy`: The new network policy to apply. async fn update_tenant_network_policy( &self, - tenant_name: &str, + tenant_id: &Id, new_policy: &TenantNetworkPolicy, ) -> Result<(), ExecutorError>; /// Decommissions an existing tenant, removing its isolated context and associated resources. /// This operation should be idempotent. - /// - /// # Arguments - /// * `tenant_name`: The logical name of the tenant to deprovision. - async fn deprovision_tenant(&self, tenant_name: &str) -> Result<(), ExecutorError>; + async fn deprovision_tenant(&self, tenant_id: &Id) -> Result<(), ExecutorError>; }