Compare commits
	
		
			No commits in common. "d1678b529eed3d77fa9b0a4b84ffbb71972f2250" and "415488ba39012cfc7ff665cad065bfb4d0475893" have entirely different histories.
		
	
	
		
			d1678b529e
			...
			415488ba39
		
	
		
@ -10,8 +10,8 @@ use harmony::{
 | 
			
		||||
async fn main() {
 | 
			
		||||
    let tenant = TenantScore {
 | 
			
		||||
        config: TenantConfig {
 | 
			
		||||
            id: Id::from_str("test-tenant-id"),
 | 
			
		||||
            name: "testtenant".to_string(),
 | 
			
		||||
            id: Id::default(),
 | 
			
		||||
            name: "TestTenant".to_string(),
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
        },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@ -27,10 +27,6 @@ impl Id {
 | 
			
		||||
    pub fn from_string(value: String) -> Self {
 | 
			
		||||
        Self { value }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn from_str(value: &str) -> Self {
 | 
			
		||||
        Self::from_string(value.to_string())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl std::fmt::Display for Id {
 | 
			
		||||
 | 
			
		||||
@ -34,10 +34,7 @@ impl K8sClient {
 | 
			
		||||
            resource.meta().name,
 | 
			
		||||
            namespace
 | 
			
		||||
        );
 | 
			
		||||
        trace!(
 | 
			
		||||
            "{:#}",
 | 
			
		||||
            serde_json::to_value(resource).unwrap_or(serde_json::Value::Null)
 | 
			
		||||
        );
 | 
			
		||||
        trace!("{:#?}", serde_json::to_string(resource));
 | 
			
		||||
 | 
			
		||||
        let api: Api<K> =
 | 
			
		||||
            <<K as Resource>::Scope as ApplyStrategy<K>>::get_api(&self.client, namespace);
 | 
			
		||||
 | 
			
		||||
@ -257,4 +257,30 @@ impl TenantManager for K8sAnywhereTopology {
 | 
			
		||||
            .provision_tenant(config)
 | 
			
		||||
            .await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn update_tenant_resource_limits(
 | 
			
		||||
        &self,
 | 
			
		||||
        tenant_id: &Id,
 | 
			
		||||
        new_limits: &ResourceLimits,
 | 
			
		||||
    ) -> Result<(), ExecutorError> {
 | 
			
		||||
        self.get_k8s_tenant_manager()?
 | 
			
		||||
            .update_tenant_resource_limits(tenant_id, new_limits)
 | 
			
		||||
            .await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn update_tenant_network_policy(
 | 
			
		||||
        &self,
 | 
			
		||||
        tenant_id: &Id,
 | 
			
		||||
        new_policy: &TenantNetworkPolicy,
 | 
			
		||||
    ) -> Result<(), ExecutorError> {
 | 
			
		||||
        self.get_k8s_tenant_manager()?
 | 
			
		||||
            .update_tenant_network_policy(tenant_id, new_policy)
 | 
			
		||||
            .await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn deprovision_tenant(&self, tenant_id: &Id) -> Result<(), ExecutorError> {
 | 
			
		||||
        self.get_k8s_tenant_manager()?
 | 
			
		||||
            .deprovision_tenant(tenant_id)
 | 
			
		||||
            .await
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,6 @@
 | 
			
		||||
use std::sync::Arc;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    executors::ExecutorError,
 | 
			
		||||
    topology::k8s::{ApplyStrategy, K8sClient},
 | 
			
		||||
};
 | 
			
		||||
use crate::{data::Id, executors::ExecutorError, topology::k8s::K8sClient};
 | 
			
		||||
use async_trait::async_trait;
 | 
			
		||||
use derive_new::new;
 | 
			
		||||
use k8s_openapi::api::{
 | 
			
		||||
@ -11,11 +8,10 @@ use k8s_openapi::api::{
 | 
			
		||||
    networking::v1::NetworkPolicy,
 | 
			
		||||
};
 | 
			
		||||
use kube::Resource;
 | 
			
		||||
use log::{debug, info, warn};
 | 
			
		||||
use serde::de::DeserializeOwned;
 | 
			
		||||
use serde_json::json;
 | 
			
		||||
 | 
			
		||||
use super::{TenantConfig, TenantManager};
 | 
			
		||||
use super::{ResourceLimits, TenantConfig, TenantManager, TenantNetworkPolicy};
 | 
			
		||||
 | 
			
		||||
#[derive(new)]
 | 
			
		||||
pub struct K8sTenantManager {
 | 
			
		||||
@ -27,40 +23,21 @@ impl K8sTenantManager {
 | 
			
		||||
        config.name.clone()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ensure_constraints(&self, _namespace: &Namespace) -> Result<(), ExecutorError> {
 | 
			
		||||
        warn!("Validate that when tenant already exists (by id) that name has not changed");
 | 
			
		||||
        warn!("Make sure other Tenant constraints are respected by this k8s implementation");
 | 
			
		||||
        Ok(())
 | 
			
		||||
    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,
 | 
			
		||||
        mut resource: K,
 | 
			
		||||
        config: &TenantConfig,
 | 
			
		||||
        resource: K,
 | 
			
		||||
    ) -> Result<K, ExecutorError>
 | 
			
		||||
    where
 | 
			
		||||
        <K as kube::Resource>::DynamicType: Default,
 | 
			
		||||
        <K as kube::Resource>::Scope: ApplyStrategy<K>,
 | 
			
		||||
    {
 | 
			
		||||
        self.apply_labels(&mut resource, config);
 | 
			
		||||
        self.k8s_client
 | 
			
		||||
            .apply(&resource, Some(&self.get_namespace_name(config)))
 | 
			
		||||
            .await
 | 
			
		||||
            .map_err(|e| {
 | 
			
		||||
                ExecutorError::UnexpectedError(format!("Could not create Tenant resource : {e}"))
 | 
			
		||||
            })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn apply_labels<K: Resource>(&self, resource: &mut K, config: &TenantConfig) {
 | 
			
		||||
        let labels = resource.meta_mut().labels.get_or_insert_default();
 | 
			
		||||
        labels.insert(
 | 
			
		||||
            "app.kubernetes.io/managed-by".to_string(),
 | 
			
		||||
            "harmony".to_string(),
 | 
			
		||||
        );
 | 
			
		||||
        labels.insert("harmony/tenant-id".to_string(), config.id.to_string());
 | 
			
		||||
        labels.insert("harmony/tenant-name".to_string(), config.name.clone());
 | 
			
		||||
        todo!("Apply tenant labels on resource and apply resource with k8s client properly")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn build_namespace(&self, config: &TenantConfig) -> Result<Namespace, ExecutorError> {
 | 
			
		||||
@ -70,7 +47,7 @@ impl K8sTenantManager {
 | 
			
		||||
                "kind": "Namespace",
 | 
			
		||||
                "metadata": {
 | 
			
		||||
                    "labels": {
 | 
			
		||||
                        "harmony.nationtech.io/tenant.id": config.id.to_string(),
 | 
			
		||||
                        "harmony.nationtech.io/tenant.id": config.id,
 | 
			
		||||
                        "harmony.nationtech.io/tenant.name": config.name,
 | 
			
		||||
                    },
 | 
			
		||||
                "name": self.get_namespace_name(config),
 | 
			
		||||
@ -87,13 +64,17 @@ impl K8sTenantManager {
 | 
			
		||||
 | 
			
		||||
    fn build_resource_quota(&self, config: &TenantConfig) -> Result<ResourceQuota, ExecutorError> {
 | 
			
		||||
        let resource_quota = json!(
 | 
			
		||||
         {
 | 
			
		||||
           "apiVersion": "v1",
 | 
			
		||||
           "kind": "List",
 | 
			
		||||
           "items": [
 | 
			
		||||
             {
 | 
			
		||||
               "apiVersion": "v1",
 | 
			
		||||
               "kind": "ResourceQuota",
 | 
			
		||||
               "metadata": {
 | 
			
		||||
            "name": format!("{}-quota", config.name),
 | 
			
		||||
                 "name": config.name,
 | 
			
		||||
                 "labels": {
 | 
			
		||||
             "harmony.nationtech.io/tenant.id": config.id.to_string(),
 | 
			
		||||
                  "harmony.nationtech.io/tenant.id": config.id,
 | 
			
		||||
                  "harmony.nationtech.io/tenant.name": config.name,
 | 
			
		||||
                 },
 | 
			
		||||
                 "namespace": self.get_namespace_name(config),
 | 
			
		||||
@ -104,7 +85,7 @@ impl K8sTenantManager {
 | 
			
		||||
                   "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}Gi", config.resource_limits.storage_total_gb),
 | 
			
		||||
                   "requests.storage": format!("{:.3}", config.resource_limits.storage_total_gb),
 | 
			
		||||
                   "pods": "20",
 | 
			
		||||
                   "services": "10",
 | 
			
		||||
                   "configmaps": "30",
 | 
			
		||||
@ -112,11 +93,12 @@ impl K8sTenantManager {
 | 
			
		||||
                   "persistentvolumeclaims": "15",
 | 
			
		||||
                   "services.loadbalancers": "2",
 | 
			
		||||
                   "services.nodeports": "5",
 | 
			
		||||
              "limits.ephemeral-storage": "10Gi",
 | 
			
		||||
 | 
			
		||||
                 }
 | 
			
		||||
               }
 | 
			
		||||
             }
 | 
			
		||||
           ]
 | 
			
		||||
         }
 | 
			
		||||
 | 
			
		||||
        );
 | 
			
		||||
        serde_json::from_value(resource_quota).map_err(|e| {
 | 
			
		||||
@ -208,20 +190,29 @@ impl TenantManager for K8sTenantManager {
 | 
			
		||||
        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!();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        debug!("Creating namespace for tenant {}", config.name);
 | 
			
		||||
        self.apply_resource(namespace, config).await?;
 | 
			
		||||
    async fn update_tenant_resource_limits(
 | 
			
		||||
        &self,
 | 
			
		||||
        tenant_id: &Id,
 | 
			
		||||
        new_limits: &ResourceLimits,
 | 
			
		||||
    ) -> Result<(), ExecutorError> {
 | 
			
		||||
        todo!()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        debug!("Creating resource_quota for tenant {}", config.name);
 | 
			
		||||
        self.apply_resource(resource_quota, config).await?;
 | 
			
		||||
    async fn update_tenant_network_policy(
 | 
			
		||||
        &self,
 | 
			
		||||
        tenant_id: &Id,
 | 
			
		||||
        new_policy: &TenantNetworkPolicy,
 | 
			
		||||
    ) -> Result<(), ExecutorError> {
 | 
			
		||||
        todo!()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        debug!("Creating network_policy for tenant {}", config.name);
 | 
			
		||||
        self.apply_resource(network_policy, config).await?;
 | 
			
		||||
 | 
			
		||||
        info!(
 | 
			
		||||
            "Success provisionning K8s tenant id {} name {}",
 | 
			
		||||
            config.id, config.name
 | 
			
		||||
        );
 | 
			
		||||
        Ok(())
 | 
			
		||||
    async fn deprovision_tenant(&self, tenant_id: &Id) -> Result<(), ExecutorError> {
 | 
			
		||||
        todo!()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -5,14 +5,31 @@ use crate::executors::ExecutorError;
 | 
			
		||||
 | 
			
		||||
#[async_trait]
 | 
			
		||||
pub trait TenantManager {
 | 
			
		||||
    /// Creates or update tenant based on the provided configuration.
 | 
			
		||||
    /// This operation should be idempotent; if a tenant with the same `config.id`
 | 
			
		||||
    /// 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.
 | 
			
		||||
    ///
 | 
			
		||||
    /// If it exists but differs, it will be updated, or return an error if the update
 | 
			
		||||
    /// action is not supported
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Arguments
 | 
			
		||||
    /// * `config`: The desired configuration for the new tenant.
 | 
			
		||||
    async fn provision_tenant(&self, config: &TenantConfig) -> Result<(), ExecutorError>;
 | 
			
		||||
 | 
			
		||||
    /// Updates the resource limits for an existing tenant.
 | 
			
		||||
    async fn update_tenant_resource_limits(
 | 
			
		||||
        &self,
 | 
			
		||||
        tenant_id: &Id,
 | 
			
		||||
        new_limits: &ResourceLimits,
 | 
			
		||||
    ) -> Result<(), ExecutorError>;
 | 
			
		||||
 | 
			
		||||
    /// Updates the high-level network isolation policy for an existing tenant.
 | 
			
		||||
    async fn update_tenant_network_policy(
 | 
			
		||||
        &self,
 | 
			
		||||
        tenant_id: &Id,
 | 
			
		||||
        new_policy: &TenantNetworkPolicy,
 | 
			
		||||
    ) -> Result<(), ExecutorError>;
 | 
			
		||||
 | 
			
		||||
    /// Decommissions an existing tenant, removing its isolated context and associated resources.
 | 
			
		||||
    /// This operation should be idempotent.
 | 
			
		||||
    async fn deprovision_tenant(&self, tenant_id: &Id) -> Result<(), ExecutorError>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,8 @@ mod manager;
 | 
			
		||||
pub use manager::*;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
 | 
			
		||||
use crate::data::Id;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] // Assuming serde for Scores
 | 
			
		||||
@ -19,6 +21,10 @@ pub struct TenantConfig {
 | 
			
		||||
 | 
			
		||||
    /// High-level network isolation policies for the tenant.
 | 
			
		||||
    pub network_policy: TenantNetworkPolicy,
 | 
			
		||||
 | 
			
		||||
    /// Key-value pairs for provider-specific tagging, labeling, or metadata.
 | 
			
		||||
    /// Useful for billing, organization, or filtering within the provider's console.
 | 
			
		||||
    pub labels_or_tags: HashMap<String, String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for TenantConfig {
 | 
			
		||||
@ -38,6 +44,7 @@ impl Default for TenantConfig {
 | 
			
		||||
                default_inter_tenant_ingress: InterTenantIngressPolicy::DenyAll,
 | 
			
		||||
                default_internet_egress: InternetEgressPolicy::AllowAll,
 | 
			
		||||
            },
 | 
			
		||||
            labels_or_tags: HashMap::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user