From 27f1a9dbdd0e3be9a0fdbb8034d63647af0315c9 Mon Sep 17 00:00:00 2001 From: Taha Hawa Date: Thu, 29 May 2025 20:15:38 +0000 Subject: [PATCH 1/9] 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)] From 60f2f31d6c4b8a3153e8160fbf28af3f80292093 Mon Sep 17 00:00:00 2001 From: Jean-Gabriel Gill-Couture Date: Fri, 30 May 2025 13:13:43 +0000 Subject: [PATCH 2/9] feat: Add TenantScore and TenantInterpret (#45) Reviewed-on: https://git.nationtech.io/NationTech/harmony/pulls/45 Co-authored-by: Jean-Gabriel Gill-Couture Co-committed-by: Jean-Gabriel Gill-Couture --- harmony/src/domain/data/id.rs | 6 +++ harmony/src/domain/interpret/mod.rs | 2 + harmony/src/modules/mod.rs | 1 + harmony/src/modules/tenant/mod.rs | 67 +++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+) create mode 100644 harmony/src/modules/tenant/mod.rs diff --git a/harmony/src/domain/data/id.rs b/harmony/src/domain/data/id.rs index a710721..e215eb4 100644 --- a/harmony/src/domain/data/id.rs +++ b/harmony/src/domain/data/id.rs @@ -10,3 +10,9 @@ impl Id { Self { value } } } + +impl std::fmt::Display for Id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.value) + } +} diff --git a/harmony/src/domain/interpret/mod.rs b/harmony/src/domain/interpret/mod.rs index d0e00d6..5e928cb 100644 --- a/harmony/src/domain/interpret/mod.rs +++ b/harmony/src/domain/interpret/mod.rs @@ -20,6 +20,7 @@ pub enum InterpretName { Panic, OPNSense, K3dInstallation, + TenantInterpret, } impl std::fmt::Display for InterpretName { @@ -35,6 +36,7 @@ impl std::fmt::Display for InterpretName { InterpretName::Panic => f.write_str("Panic"), InterpretName::OPNSense => f.write_str("OPNSense"), InterpretName::K3dInstallation => f.write_str("K3dInstallation"), + InterpretName::TenantInterpret => f.write_str("Tenant"), } } } diff --git a/harmony/src/modules/mod.rs b/harmony/src/modules/mod.rs index 07f489e..1427515 100644 --- a/harmony/src/modules/mod.rs +++ b/harmony/src/modules/mod.rs @@ -12,4 +12,5 @@ pub mod load_balancer; pub mod monitoring; pub mod okd; pub mod opnsense; +pub mod tenant; pub mod tftp; diff --git a/harmony/src/modules/tenant/mod.rs b/harmony/src/modules/tenant/mod.rs new file mode 100644 index 0000000..5ee212c --- /dev/null +++ b/harmony/src/modules/tenant/mod.rs @@ -0,0 +1,67 @@ +use async_trait::async_trait; +use serde::Serialize; + +use crate::{ + data::{Id, Version}, + interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, + inventory::Inventory, + score::Score, + topology::{ + Topology, + tenant::{TenantConfig, TenantManager}, + }, +}; + +#[derive(Debug, Serialize, Clone)] +pub struct TenantScore { + config: TenantConfig, +} + +impl Score for TenantScore { + fn create_interpret(&self) -> Box> { + Box::new(TenantInterpret { + tenant_config: self.config.clone(), + }) + } + + fn name(&self) -> String { + format!("{} TenantScore", self.config.name) + } +} + +#[derive(Debug)] +pub struct TenantInterpret { + tenant_config: TenantConfig, +} + +#[async_trait] +impl Interpret for TenantInterpret { + async fn execute( + &self, + _inventory: &Inventory, + topology: &T, + ) -> Result { + topology.provision_tenant(&self.tenant_config).await?; + + Ok(Outcome::success(format!( + "Successfully provisioned tenant {} with id {}", + self.tenant_config.name, self.tenant_config.id + ))) + } + + fn get_name(&self) -> InterpretName { + InterpretName::TenantInterpret + } + + fn get_version(&self) -> Version { + todo!() + } + + fn get_status(&self) -> InterpretStatus { + todo!() + } + + fn get_children(&self) -> Vec { + todo!() + } +} From b631e8ccbbe97220e5339c271f9d0ada56121375 Mon Sep 17 00:00:00 2001 From: Willem Date: Fri, 30 May 2025 13:21:38 -0400 Subject: [PATCH 3/9] feat: Initial setup for monitoring and alerting --- harmony/Cargo.toml | 1 + harmony/src/domain/topology/k8s_anywhere.rs | 24 ++++++-- harmony/src/domain/topology/mod.rs | 1 + .../domain/topology/oberservability/mod.rs | 16 +++++ .../topology/oberservability/monitoring.rs | 60 +++++++++++++++++++ .../notification_adapter_deployer.rs | 12 ++++ 6 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 harmony/src/domain/topology/oberservability/mod.rs create mode 100644 harmony/src/domain/topology/oberservability/monitoring.rs create mode 100644 harmony/src/domain/topology/oberservability/notification_adapter_deployer.rs diff --git a/harmony/Cargo.toml b/harmony/Cargo.toml index 5bc88b1..fcf69cf 100644 --- a/harmony/Cargo.toml +++ b/harmony/Cargo.toml @@ -49,3 +49,4 @@ fqdn = { version = "0.4.6", features = [ "serde", ] } temp-dir = "0.1.14" +dyn-clone = "1.0.19" diff --git a/harmony/src/domain/topology/k8s_anywhere.rs b/harmony/src/domain/topology/k8s_anywhere.rs index 369f030..5df2bc8 100644 --- a/harmony/src/domain/topology/k8s_anywhere.rs +++ b/harmony/src/domain/topology/k8s_anywhere.rs @@ -15,11 +15,9 @@ use crate::{ }; use super::{ - HelmCommand, K8sclient, Topology, - k8s::K8sClient, - tenant::{ - ResourceLimits, TenantConfig, TenantManager, TenantNetworkPolicy, k8s::K8sTenantManager, - }, + k8s::K8sClient, oberservability::notification_adapter_deployer::NotificationAdapterDeployer, tenant::{ + k8s::K8sTenantManager, ResourceLimits, TenantConfig, TenantManager, TenantNetworkPolicy + }, HelmCommand, K8sclient, Topology }; struct K8sState { @@ -263,3 +261,19 @@ impl TenantManager for K8sAnywhereTopology { .await } } + +#[async_trait] +impl NotificationAdapterDeployer for K8sAnywhereTopology { + fn deploy_notification_adapter( + &self, + _notification_adapter_id: &str, + ) -> Result { + todo!() + } + fn remove_notification_adapter( + &self, + _notification_adapter_id: &str, + ) -> Result { + todo!() + } +} diff --git a/harmony/src/domain/topology/mod.rs b/harmony/src/domain/topology/mod.rs index abf317d..2dcd501 100644 --- a/harmony/src/domain/topology/mod.rs +++ b/harmony/src/domain/topology/mod.rs @@ -4,6 +4,7 @@ mod http; mod k8s_anywhere; mod localhost; pub mod tenant; +pub mod oberservability; pub use k8s_anywhere::*; pub use localhost::*; pub mod k8s; diff --git a/harmony/src/domain/topology/oberservability/mod.rs b/harmony/src/domain/topology/oberservability/mod.rs new file mode 100644 index 0000000..6ab1b2c --- /dev/null +++ b/harmony/src/domain/topology/oberservability/mod.rs @@ -0,0 +1,16 @@ +use monitoring::AlertChannelConfig; + +pub mod monitoring; +pub mod notification_adapter_deployer; + +pub enum MonitoringBackendType { + KubePrometheus, +} + +pub struct MonitorConfig { + pub backend: MonitoringBackendType, + pub alert_channels: Vec>, +} + + + diff --git a/harmony/src/domain/topology/oberservability/monitoring.rs b/harmony/src/domain/topology/oberservability/monitoring.rs new file mode 100644 index 0000000..8ad88f6 --- /dev/null +++ b/harmony/src/domain/topology/oberservability/monitoring.rs @@ -0,0 +1,60 @@ +use async_trait::async_trait; + +use std::fmt::Debug; +use dyn_clone::DynClone; +use serde_json::Value; + +use crate::interpret::InterpretError; + +use crate::{ + interpret::Outcome, + topology::Topology, +}; + +use super::notification_adapter_deployer::NotificationAdapterDeployer; +use super::{MonitorConfig, MonitoringBackendType}; + +#[async_trait] +pub trait Monitor { + async fn provision_monitor( + &self, + topology: &T, + monitor_config: &MonitorConfig, + ) -> Result; + + async fn delete_monitor( + &self, + topolgy: &T, + monitor_config: &MonitorConfig, + ) -> Result; + + async fn configure_alerting( + &self, + topology: &T, + monitor_config: &MonitorConfig, + ) -> Result; + + async fn ensure_alert_channel_dependencies( + &self, + topology: &T, + monitor_config: &MonitorConfig, + ) -> Result { + for channel in &monitor_config.alert_channels { + if let Some(notification_adapter_id) = + channel.requires_external_alert_channel_adapter(&monitor_config.backend) + { + topology.deploy_notification_adapter( + ¬ification_adapter_id.as_ref(), + )?; + } + + } + Ok(Outcome::success(format!("deployed alert channels {:?}", &monitor_config.alert_channels))) + } +} + +pub trait AlertChannelConfig: Debug + DynClone + Send + Sync { + fn build_backend_integration_config(&self, backend: &MonitoringBackendType) -> Result; + fn requires_external_alert_channel_adapter(&self, backend: &MonitoringBackendType) -> Option; +} + diff --git a/harmony/src/domain/topology/oberservability/notification_adapter_deployer.rs b/harmony/src/domain/topology/oberservability/notification_adapter_deployer.rs new file mode 100644 index 0000000..02786ae --- /dev/null +++ b/harmony/src/domain/topology/oberservability/notification_adapter_deployer.rs @@ -0,0 +1,12 @@ +use crate::interpret::{InterpretError, Outcome}; + +pub trait NotificationAdapterDeployer { + fn deploy_notification_adapter( + &self, + notification_adapter_id: &str, + ) -> Result; + fn remove_notification_adapter( + &self, + notication_adapter_id: &str, + ) -> Result; +} From 7e3f1b183090604abe3c0bdd549101a57b08e19c Mon Sep 17 00:00:00 2001 From: Willem Date: Fri, 30 May 2025 13:59:29 -0400 Subject: [PATCH 4/9] fix:cargo fmt --- harmony/src/domain/topology/k8s_anywhere.rs | 9 ++++-- harmony/src/domain/topology/mod.rs | 2 +- .../domain/topology/oberservability/mod.rs | 3 -- .../topology/oberservability/monitoring.rs | 28 ++++++++++--------- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/harmony/src/domain/topology/k8s_anywhere.rs b/harmony/src/domain/topology/k8s_anywhere.rs index 5df2bc8..4c92b0c 100644 --- a/harmony/src/domain/topology/k8s_anywhere.rs +++ b/harmony/src/domain/topology/k8s_anywhere.rs @@ -15,9 +15,12 @@ use crate::{ }; use super::{ - k8s::K8sClient, oberservability::notification_adapter_deployer::NotificationAdapterDeployer, tenant::{ - k8s::K8sTenantManager, ResourceLimits, TenantConfig, TenantManager, TenantNetworkPolicy - }, HelmCommand, K8sclient, Topology + HelmCommand, K8sclient, Topology, + k8s::K8sClient, + oberservability::notification_adapter_deployer::NotificationAdapterDeployer, + tenant::{ + ResourceLimits, TenantConfig, TenantManager, TenantNetworkPolicy, k8s::K8sTenantManager, + }, }; struct K8sState { diff --git a/harmony/src/domain/topology/mod.rs b/harmony/src/domain/topology/mod.rs index 2dcd501..faa7fee 100644 --- a/harmony/src/domain/topology/mod.rs +++ b/harmony/src/domain/topology/mod.rs @@ -3,8 +3,8 @@ mod host_binding; mod http; mod k8s_anywhere; mod localhost; -pub mod tenant; pub mod oberservability; +pub mod tenant; pub use k8s_anywhere::*; pub use localhost::*; pub mod k8s; diff --git a/harmony/src/domain/topology/oberservability/mod.rs b/harmony/src/domain/topology/oberservability/mod.rs index 6ab1b2c..aa75d09 100644 --- a/harmony/src/domain/topology/oberservability/mod.rs +++ b/harmony/src/domain/topology/oberservability/mod.rs @@ -11,6 +11,3 @@ pub struct MonitorConfig { pub backend: MonitoringBackendType, pub alert_channels: Vec>, } - - - diff --git a/harmony/src/domain/topology/oberservability/monitoring.rs b/harmony/src/domain/topology/oberservability/monitoring.rs index 8ad88f6..9e38358 100644 --- a/harmony/src/domain/topology/oberservability/monitoring.rs +++ b/harmony/src/domain/topology/oberservability/monitoring.rs @@ -1,15 +1,12 @@ use async_trait::async_trait; -use std::fmt::Debug; use dyn_clone::DynClone; use serde_json::Value; +use std::fmt::Debug; use crate::interpret::InterpretError; -use crate::{ - interpret::Outcome, - topology::Topology, -}; +use crate::{interpret::Outcome, topology::Topology}; use super::notification_adapter_deployer::NotificationAdapterDeployer; use super::{MonitorConfig, MonitoringBackendType}; @@ -43,18 +40,23 @@ pub trait Monitor { if let Some(notification_adapter_id) = channel.requires_external_alert_channel_adapter(&monitor_config.backend) { - topology.deploy_notification_adapter( - ¬ification_adapter_id.as_ref(), - )?; + topology.deploy_notification_adapter(¬ification_adapter_id.as_ref())?; } - } - Ok(Outcome::success(format!("deployed alert channels {:?}", &monitor_config.alert_channels))) + Ok(Outcome::success(format!( + "deployed alert channels {:?}", + &monitor_config.alert_channels + ))) } } pub trait AlertChannelConfig: Debug + DynClone + Send + Sync { - fn build_backend_integration_config(&self, backend: &MonitoringBackendType) -> Result; - fn requires_external_alert_channel_adapter(&self, backend: &MonitoringBackendType) -> Option; + fn build_backend_integration_config( + &self, + backend: &MonitoringBackendType, + ) -> Result; + fn requires_external_alert_channel_adapter( + &self, + backend: &MonitoringBackendType, + ) -> Option; } - From 691540fe64ebecd38d4c2939f30b83baa17962b8 Mon Sep 17 00:00:00 2001 From: Willem Date: Mon, 2 Jun 2025 11:42:37 -0400 Subject: [PATCH 5/9] wip: modified initial monitoring architecture based on pr review --- harmony/src/domain/topology/k8s_anywhere.rs | 19 +------ .../domain/topology/oberservability/mod.rs | 12 ---- .../topology/oberservability/monitoring.rs | 53 ++++-------------- .../notification_adapter_deployer.rs | 12 ---- .../monitoring/discord_webhook_sender.rs | 56 +++++++++++++++++++ harmony/src/modules/monitoring/mod.rs | 4 +- 6 files changed, 70 insertions(+), 86 deletions(-) delete mode 100644 harmony/src/domain/topology/oberservability/notification_adapter_deployer.rs create mode 100644 harmony/src/modules/monitoring/discord_webhook_sender.rs diff --git a/harmony/src/domain/topology/k8s_anywhere.rs b/harmony/src/domain/topology/k8s_anywhere.rs index 4c92b0c..ef11f36 100644 --- a/harmony/src/domain/topology/k8s_anywhere.rs +++ b/harmony/src/domain/topology/k8s_anywhere.rs @@ -1,4 +1,4 @@ -use std::{io::Error, process::Command, sync::Arc}; +use std::{process::Command, sync::Arc}; use async_trait::async_trait; use inquire::Confirm; @@ -17,7 +17,6 @@ use crate::{ use super::{ HelmCommand, K8sclient, Topology, k8s::K8sClient, - oberservability::notification_adapter_deployer::NotificationAdapterDeployer, tenant::{ ResourceLimits, TenantConfig, TenantManager, TenantNetworkPolicy, k8s::K8sTenantManager, }, @@ -264,19 +263,3 @@ impl TenantManager for K8sAnywhereTopology { .await } } - -#[async_trait] -impl NotificationAdapterDeployer for K8sAnywhereTopology { - fn deploy_notification_adapter( - &self, - _notification_adapter_id: &str, - ) -> Result { - todo!() - } - fn remove_notification_adapter( - &self, - _notification_adapter_id: &str, - ) -> Result { - todo!() - } -} diff --git a/harmony/src/domain/topology/oberservability/mod.rs b/harmony/src/domain/topology/oberservability/mod.rs index aa75d09..7f2ac95 100644 --- a/harmony/src/domain/topology/oberservability/mod.rs +++ b/harmony/src/domain/topology/oberservability/mod.rs @@ -1,13 +1 @@ -use monitoring::AlertChannelConfig; - pub mod monitoring; -pub mod notification_adapter_deployer; - -pub enum MonitoringBackendType { - KubePrometheus, -} - -pub struct MonitorConfig { - pub backend: MonitoringBackendType, - pub alert_channels: Vec>, -} diff --git a/harmony/src/domain/topology/oberservability/monitoring.rs b/harmony/src/domain/topology/oberservability/monitoring.rs index 9e38358..485f633 100644 --- a/harmony/src/domain/topology/oberservability/monitoring.rs +++ b/harmony/src/domain/topology/oberservability/monitoring.rs @@ -1,62 +1,33 @@ use async_trait::async_trait; use dyn_clone::DynClone; -use serde_json::Value; use std::fmt::Debug; +use url::Url; use crate::interpret::InterpretError; use crate::{interpret::Outcome, topology::Topology}; -use super::notification_adapter_deployer::NotificationAdapterDeployer; -use super::{MonitorConfig, MonitoringBackendType}; - #[async_trait] -pub trait Monitor { - async fn provision_monitor( +pub trait Monitor: Debug + Send + Sync { + async fn deploy_monitor( &self, topology: &T, - monitor_config: &MonitorConfig, + config: Vec>>, ) -> Result; async fn delete_monitor( &self, topolgy: &T, - monitor_config: &MonitorConfig, + config: Vec>>, ) -> Result; - - async fn configure_alerting( - &self, - topology: &T, - monitor_config: &MonitorConfig, - ) -> Result; - - async fn ensure_alert_channel_dependencies( - &self, - topology: &T, - monitor_config: &MonitorConfig, - ) -> Result { - for channel in &monitor_config.alert_channels { - if let Some(notification_adapter_id) = - channel.requires_external_alert_channel_adapter(&monitor_config.backend) - { - topology.deploy_notification_adapter(¬ification_adapter_id.as_ref())?; - } - } - Ok(Outcome::success(format!( - "deployed alert channels {:?}", - &monitor_config.alert_channels - ))) - } } -pub trait AlertChannelConfig: Debug + DynClone + Send + Sync { - fn build_backend_integration_config( - &self, - backend: &MonitoringBackendType, - ) -> Result; - fn requires_external_alert_channel_adapter( - &self, - backend: &MonitoringBackendType, - ) -> Option; +#[async_trait] +pub trait AlertChannelConfig: Debug + DynClone + Send + Sync { + fn channel_identifier(&self) -> String; + + fn webhook_url(&self) -> Option; + + fn send_resolved_notifications(&self) -> bool; } diff --git a/harmony/src/domain/topology/oberservability/notification_adapter_deployer.rs b/harmony/src/domain/topology/oberservability/notification_adapter_deployer.rs deleted file mode 100644 index 02786ae..0000000 --- a/harmony/src/domain/topology/oberservability/notification_adapter_deployer.rs +++ /dev/null @@ -1,12 +0,0 @@ -use crate::interpret::{InterpretError, Outcome}; - -pub trait NotificationAdapterDeployer { - fn deploy_notification_adapter( - &self, - notification_adapter_id: &str, - ) -> Result; - fn remove_notification_adapter( - &self, - notication_adapter_id: &str, - ) -> Result; -} diff --git a/harmony/src/modules/monitoring/discord_webhook_sender.rs b/harmony/src/modules/monitoring/discord_webhook_sender.rs new file mode 100644 index 0000000..e5eea07 --- /dev/null +++ b/harmony/src/modules/monitoring/discord_webhook_sender.rs @@ -0,0 +1,56 @@ +use async_trait::async_trait; +use url::Url; + +use crate::{ + interpret::{InterpretError, Outcome}, + topology::{K8sAnywhereTopology, Topology, oberservability::monitoring::AlertChannelConfig}, +}; + +#[derive(Debug, Clone)] +pub struct DiscordWebhookConfig { + pub webhook_url: Url, + pub name: String, + pub send_resolved_notifications: bool, +} + +pub trait DiscordWebhookSender { + fn deploy_discord_webhook_sender( + &self, + _notification_adapter_id: &str, + ) -> Result; + + fn delete_discord_webhook_sender( + &self, + _notification_adapter_id: &str, + ) -> Result; +} + +#[async_trait] +impl AlertChannelConfig for DiscordWebhookConfig { + fn channel_identifier(&self) -> String { + self.name.clone() + } + fn webhook_url(&self) -> Option { + Some(self.webhook_url.clone()) + } + + fn send_resolved_notifications(&self) -> bool { + self.send_resolved_notifications.clone() + } +} + +#[async_trait] +impl DiscordWebhookSender for K8sAnywhereTopology { + fn deploy_discord_webhook_sender( + &self, + _notification_adapter_id: &str, + ) -> Result { + todo!() + } + fn delete_discord_webhook_sender( + &self, + _notification_adapter_id: &str, + ) -> Result { + todo!() + } +} diff --git a/harmony/src/modules/monitoring/mod.rs b/harmony/src/modules/monitoring/mod.rs index 914ae07..56c7f4d 100644 --- a/harmony/src/modules/monitoring/mod.rs +++ b/harmony/src/modules/monitoring/mod.rs @@ -1,4 +1,2 @@ mod config; -mod discord_alert_manager; -mod kube_prometheus; -pub mod monitoring_alerting; +pub mod discord_webhook_sender; From 56dc1e93c16a3b16cf1edbfb0b7f1f6e6c6faec2 Mon Sep 17 00:00:00 2001 From: Willem Date: Mon, 2 Jun 2025 11:47:21 -0400 Subject: [PATCH 6/9] fix: modified files in mod --- harmony/src/modules/monitoring/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/harmony/src/modules/monitoring/mod.rs b/harmony/src/modules/monitoring/mod.rs index 56c7f4d..d3eb288 100644 --- a/harmony/src/modules/monitoring/mod.rs +++ b/harmony/src/modules/monitoring/mod.rs @@ -1,2 +1,5 @@ mod config; +mod discord_alert_manager; pub mod discord_webhook_sender; +mod kube_prometheus; +pub mod monitoring_alerting; From 0d56fbc09d6c46b9b2e8ff6f93fb4604325c6b54 Mon Sep 17 00:00:00 2001 From: Willem Date: Mon, 2 Jun 2025 14:44:43 -0400 Subject: [PATCH 7/9] wip: applied comments in pr, changed naming of AlertChannel to AlertReceiver and added rust doc to Monitor for clarity --- .../topology/oberservability/monitoring.rs | 22 ++++++---- .../monitoring/discord_alert_manager.rs | 31 ++++---------- .../monitoring/discord_webhook_sender.rs | 34 +++++++-------- .../modules/monitoring/monitoring_alerting.rs | 42 +++++++++---------- 4 files changed, 61 insertions(+), 68 deletions(-) diff --git a/harmony/src/domain/topology/oberservability/monitoring.rs b/harmony/src/domain/topology/oberservability/monitoring.rs index 485f633..35e8b33 100644 --- a/harmony/src/domain/topology/oberservability/monitoring.rs +++ b/harmony/src/domain/topology/oberservability/monitoring.rs @@ -8,26 +8,32 @@ use crate::interpret::InterpretError; use crate::{interpret::Outcome, topology::Topology}; +/// Represents an entity responsible for collecting and organizing observability data +/// from various telemetry sources +/// A `Monitor` abstracts the logic required to scrape, aggregate, and structure +/// monitoring data, enabling consistent processing regardless of the underlying data source. #[async_trait] pub trait Monitor: Debug + Send + Sync { async fn deploy_monitor( &self, topology: &T, - config: Vec>>, + alert_receivers: Vec>>, ) -> Result; async fn delete_monitor( &self, topolgy: &T, - config: Vec>>, + alert_receivers: Vec>>, ) -> Result; } #[async_trait] -pub trait AlertChannelConfig: Debug + DynClone + Send + Sync { - fn channel_identifier(&self) -> String; - - fn webhook_url(&self) -> Option; - - fn send_resolved_notifications(&self) -> bool; +pub trait AlertReceiver: Debug + DynClone + Send + Sync { + fn get_alert_receiver_config(&self) -> AlertReceiverConfig; +} + +pub struct AlertReceiverConfig { + pub receiver_id: String, + pub receiver_webhook_url: Option, + pub send_resolved: bool, } diff --git a/harmony/src/modules/monitoring/discord_alert_manager.rs b/harmony/src/modules/monitoring/discord_alert_manager.rs index a3519e8..7765505 100644 --- a/harmony/src/modules/monitoring/discord_alert_manager.rs +++ b/harmony/src/modules/monitoring/discord_alert_manager.rs @@ -1,30 +1,25 @@ use std::str::FromStr; use non_blank_string_rs::NonBlankString; +use url::Url; use crate::modules::helm::chart::HelmChartScore; -use super::{config::KubePrometheusConfig, monitoring_alerting::AlertChannel}; - -fn get_discord_alert_manager_score(config: &KubePrometheusConfig) -> Option { - let (url, name) = config.alert_channel.iter().find_map(|channel| { - if let AlertChannel::Discord { webhook_url, name } = channel { - Some((webhook_url, name)) - } else { - None - } - })?; - +pub fn discord_alert_manager_score( + webhook_url: Url, + namespace: String, + name: String, +) -> HelmChartScore { let values = format!( r#" environment: - name: "DISCORD_WEBHOOK" - value: "{url}" + value: "{webhook_url}" "#, ); - Some(HelmChartScore { - namespace: Some(NonBlankString::from_str(&config.namespace).unwrap()), + HelmChartScore { + namespace: Some(NonBlankString::from_str(&namespace).unwrap()), release_name: NonBlankString::from_str(&name).unwrap(), chart_name: NonBlankString::from_str( "oci://hub.nationtech.io/library/alertmanager-discord", @@ -36,13 +31,5 @@ environment: create_namespace: true, install_only: true, repository: None, - }) -} - -pub fn discord_alert_manager_score(config: &KubePrometheusConfig) -> HelmChartScore { - if let Some(chart) = get_discord_alert_manager_score(config) { - chart - } else { - panic!("Expected discord alert manager helm chart"); } } diff --git a/harmony/src/modules/monitoring/discord_webhook_sender.rs b/harmony/src/modules/monitoring/discord_webhook_sender.rs index e5eea07..28bdcb9 100644 --- a/harmony/src/modules/monitoring/discord_webhook_sender.rs +++ b/harmony/src/modules/monitoring/discord_webhook_sender.rs @@ -3,7 +3,10 @@ use url::Url; use crate::{ interpret::{InterpretError, Outcome}, - topology::{K8sAnywhereTopology, Topology, oberservability::monitoring::AlertChannelConfig}, + topology::{ + K8sAnywhereTopology, Topology, + oberservability::monitoring::{AlertReceiver, AlertReceiverConfig}, + }, }; #[derive(Debug, Clone)] @@ -13,41 +16,38 @@ pub struct DiscordWebhookConfig { pub send_resolved_notifications: bool, } -pub trait DiscordWebhookSender { - fn deploy_discord_webhook_sender( +pub trait DiscordWebhookReceiver { + fn deploy_discord_webhook_receiver( &self, _notification_adapter_id: &str, ) -> Result; - fn delete_discord_webhook_sender( + fn delete_discord_webhook_receiver( &self, _notification_adapter_id: &str, ) -> Result; } #[async_trait] -impl AlertChannelConfig for DiscordWebhookConfig { - fn channel_identifier(&self) -> String { - self.name.clone() - } - fn webhook_url(&self) -> Option { - Some(self.webhook_url.clone()) - } - - fn send_resolved_notifications(&self) -> bool { - self.send_resolved_notifications.clone() +impl AlertReceiver for DiscordWebhookConfig { + fn get_alert_receiver_config(&self) -> AlertReceiverConfig { + AlertReceiverConfig { + receiver_id: self.name.clone(), + receiver_webhook_url: Some(self.webhook_url.clone()), + send_resolved: self.send_resolved_notifications.clone(), + } } } #[async_trait] -impl DiscordWebhookSender for K8sAnywhereTopology { - fn deploy_discord_webhook_sender( +impl DiscordWebhookReceiver for K8sAnywhereTopology { + fn deploy_discord_webhook_receiver( &self, _notification_adapter_id: &str, ) -> Result { todo!() } - fn delete_discord_webhook_sender( + fn delete_discord_webhook_receiver( &self, _notification_adapter_id: &str, ) -> Result { diff --git a/harmony/src/modules/monitoring/monitoring_alerting.rs b/harmony/src/modules/monitoring/monitoring_alerting.rs index 325f9d3..6d2db38 100644 --- a/harmony/src/modules/monitoring/monitoring_alerting.rs +++ b/harmony/src/modules/monitoring/monitoring_alerting.rs @@ -96,28 +96,28 @@ impl MonitoringAlertingStackInterpret { topology: &T, config: &KubePrometheusConfig, ) -> Result { - let mut outcomes = vec![]; + //let mut outcomes = vec![]; - for channel in &self.score.alert_channel { - let outcome = match channel { - AlertChannel::Discord { .. } => { - discord_alert_manager_score(config) - .create_interpret() - .execute(inventory, topology) - .await - } - AlertChannel::Slack { .. } => Ok(Outcome::success( - "No extra configs for slack alerting".to_string(), - )), - AlertChannel::Smpt { .. } => { - todo!() - } - }; - outcomes.push(outcome); - } - for result in outcomes { - result?; - } + //for channel in &self.score.alert_channel { + // let outcome = match channel { + // AlertChannel::Discord { .. } => { + // discord_alert_manager_score(config) + // .create_interpret() + // .execute(inventory, topology) + // .await + // } + // AlertChannel::Slack { .. } => Ok(Outcome::success( + // "No extra configs for slack alerting".to_string(), + // )), + // AlertChannel::Smpt { .. } => { + // todo!() + // } + // }; + // outcomes.push(outcome); + //} + //for result in outcomes { + // result?; + //} Ok(Outcome::success("All alert channels deployed".to_string())) } From a2be9457b9ac02330ff3aebf4e2f34b64ee07450 Mon Sep 17 00:00:00 2001 From: Willem Date: Mon, 2 Jun 2025 16:11:36 -0400 Subject: [PATCH 8/9] wip: removed AlertReceiverConfig --- .../topology/oberservability/monitoring.rs | 16 ++++--------- .../monitoring/discord_webhook_sender.rs | 24 ++++++++++--------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/harmony/src/domain/topology/oberservability/monitoring.rs b/harmony/src/domain/topology/oberservability/monitoring.rs index 35e8b33..6646c5a 100644 --- a/harmony/src/domain/topology/oberservability/monitoring.rs +++ b/harmony/src/domain/topology/oberservability/monitoring.rs @@ -1,6 +1,5 @@ use async_trait::async_trait; -use dyn_clone::DynClone; use std::fmt::Debug; use url::Url; @@ -9,7 +8,7 @@ use crate::interpret::InterpretError; use crate::{interpret::Outcome, topology::Topology}; /// Represents an entity responsible for collecting and organizing observability data -/// from various telemetry sources +/// from various telemetry sources /// A `Monitor` abstracts the logic required to scrape, aggregate, and structure /// monitoring data, enabling consistent processing regardless of the underlying data source. #[async_trait] @@ -17,23 +16,16 @@ pub trait Monitor: Debug + Send + Sync { async fn deploy_monitor( &self, topology: &T, - alert_receivers: Vec>>, + alert_receivers: Vec, ) -> Result; async fn delete_monitor( &self, topolgy: &T, - alert_receivers: Vec>>, + alert_receivers: Vec, ) -> Result; } -#[async_trait] -pub trait AlertReceiver: Debug + DynClone + Send + Sync { - fn get_alert_receiver_config(&self) -> AlertReceiverConfig; -} - -pub struct AlertReceiverConfig { +pub struct AlertReceiver { pub receiver_id: String, - pub receiver_webhook_url: Option, - pub send_resolved: bool, } diff --git a/harmony/src/modules/monitoring/discord_webhook_sender.rs b/harmony/src/modules/monitoring/discord_webhook_sender.rs index 28bdcb9..1b12fac 100644 --- a/harmony/src/modules/monitoring/discord_webhook_sender.rs +++ b/harmony/src/modules/monitoring/discord_webhook_sender.rs @@ -1,12 +1,12 @@ use async_trait::async_trait; +use serde_json::Value; use url::Url; use crate::{ interpret::{InterpretError, Outcome}, - topology::{ - K8sAnywhereTopology, Topology, - oberservability::monitoring::{AlertReceiver, AlertReceiverConfig}, - }, + topology:: + K8sAnywhereTopology + , }; #[derive(Debug, Clone)] @@ -28,14 +28,15 @@ pub trait DiscordWebhookReceiver { ) -> Result; } +// trait used to generate alert manager config values impl Monitor for KubePrometheus +pub trait AlertManagerConfig { + fn get_alert_manager_config(&self) -> Result; +} + #[async_trait] -impl AlertReceiver for DiscordWebhookConfig { - fn get_alert_receiver_config(&self) -> AlertReceiverConfig { - AlertReceiverConfig { - receiver_id: self.name.clone(), - receiver_webhook_url: Some(self.webhook_url.clone()), - send_resolved: self.send_resolved_notifications.clone(), - } +impl AlertManagerConfig for DiscordWebhookConfig { + fn get_alert_manager_config(&self) -> Result{ + todo!() } } @@ -54,3 +55,4 @@ impl DiscordWebhookReceiver for K8sAnywhereTopology { todo!() } } + From 12eb4ae31f7a785daea1edd1e0784ce2420c3b55 Mon Sep 17 00:00:00 2001 From: Willem Date: Mon, 2 Jun 2025 16:20:49 -0400 Subject: [PATCH 9/9] fix: cargo fmt --- .../src/domain/topology/oberservability/monitoring.rs | 2 +- harmony/src/modules/monitoring/discord_webhook_sender.rs | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/harmony/src/domain/topology/oberservability/monitoring.rs b/harmony/src/domain/topology/oberservability/monitoring.rs index 6646c5a..4603eba 100644 --- a/harmony/src/domain/topology/oberservability/monitoring.rs +++ b/harmony/src/domain/topology/oberservability/monitoring.rs @@ -8,7 +8,7 @@ use crate::interpret::InterpretError; use crate::{interpret::Outcome, topology::Topology}; /// Represents an entity responsible for collecting and organizing observability data -/// from various telemetry sources +/// from various telemetry sources /// A `Monitor` abstracts the logic required to scrape, aggregate, and structure /// monitoring data, enabling consistent processing regardless of the underlying data source. #[async_trait] diff --git a/harmony/src/modules/monitoring/discord_webhook_sender.rs b/harmony/src/modules/monitoring/discord_webhook_sender.rs index 1b12fac..bad6402 100644 --- a/harmony/src/modules/monitoring/discord_webhook_sender.rs +++ b/harmony/src/modules/monitoring/discord_webhook_sender.rs @@ -4,9 +4,7 @@ use url::Url; use crate::{ interpret::{InterpretError, Outcome}, - topology:: - K8sAnywhereTopology - , + topology::K8sAnywhereTopology, }; #[derive(Debug, Clone)] @@ -35,8 +33,8 @@ pub trait AlertManagerConfig { #[async_trait] impl AlertManagerConfig for DiscordWebhookConfig { - fn get_alert_manager_config(&self) -> Result{ - todo!() + fn get_alert_manager_config(&self) -> Result { + todo!() } } @@ -55,4 +53,3 @@ impl DiscordWebhookReceiver for K8sAnywhereTopology { todo!() } } -