feat: Tenant manager k8s implementation progress : ResourceQuota, NetworkPolicy and Namespace look good. Still WIP

This commit is contained in:
Jean-Gabriel Gill-Couture 2025-06-09 13:59:49 -04:00
parent 8c65aef127
commit 6cf61ae67c
2 changed files with 129 additions and 29 deletions

View File

@ -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<K8sClient>,
}
#[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<K, ExecutorError>
where
<K as kube::Resource>::DynamicType: Default,
{
todo!("Apply tenant labels on resource and apply resource with k8s client properly")
}
fn build_namespace(&self, config: &TenantConfig) -> Result<Namespace, ExecutorError> {
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<ResourceQuota, ExecutorError> {
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<NetworkPolicy, ExecutorError> {
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!()
}
}

View File

@ -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>;
}