From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: <pve-devel-bounces@lists.proxmox.com> Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 550EA1FF164 for <inbox@lore.proxmox.com>; Fri, 28 Mar 2025 18:18:26 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id DA13F96E7; Fri, 28 Mar 2025 18:14:35 +0100 (CET) From: Gabriel Goller <g.goller@proxmox.com> To: pve-devel@lists.proxmox.com Date: Fri, 28 Mar 2025 18:13:09 +0100 Message-Id: <20250328171340.885413-22-g.goller@proxmox.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250328171340.885413-1-g.goller@proxmox.com> References: <20250328171340.885413-1-g.goller@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.025 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pve-devel] [PATCH proxmox-perl-rs 2/7] perl-rs: sdn: add CRUD helpers for OpenFabric fabric management X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion <pve-devel.lists.proxmox.com> List-Unsubscribe: <https://lists.proxmox.com/cgi-bin/mailman/options/pve-devel>, <mailto:pve-devel-request@lists.proxmox.com?subject=unsubscribe> List-Archive: <http://lists.proxmox.com/pipermail/pve-devel/> List-Post: <mailto:pve-devel@lists.proxmox.com> List-Help: <mailto:pve-devel-request@lists.proxmox.com?subject=help> List-Subscribe: <https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel>, <mailto:pve-devel-request@lists.proxmox.com?subject=subscribe> Reply-To: Proxmox VE development discussion <pve-devel@lists.proxmox.com> Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" <pve-devel-bounces@lists.proxmox.com> Add functionality for managing OpenFabric fabrics: - Implement Rust-backed Perl module PVE::RS::SDN::Fabrics::OpenFabric - Add CRUD methods for fabric, node, and interface configuration - Support fabric-specific parameters (hello-intervals, router-id, etc.) Signed-off-by: Gabriel Goller <g.goller@proxmox.com> --- pve-rs/Makefile | 1 + pve-rs/src/sdn/mod.rs | 1 + pve-rs/src/sdn/openfabric.rs | 224 +++++++++++++++++++++++++++++++++++ 3 files changed, 226 insertions(+) create mode 100644 pve-rs/src/sdn/openfabric.rs diff --git a/pve-rs/Makefile b/pve-rs/Makefile index 86af16eb5e04..6bd9c8a2acec 100644 --- a/pve-rs/Makefile +++ b/pve-rs/Makefile @@ -32,6 +32,7 @@ PERLMOD_PACKAGES := \ PVE::RS::OpenId \ PVE::RS::ResourceScheduling::Static \ PVE::RS::SDN::Fabrics \ + PVE::RS::SDN::Fabrics::OpenFabric \ PVE::RS::TFA PERLMOD_PACKAGE_FILES := $(addsuffix .pm,$(subst ::,/,$(PERLMOD_PACKAGES))) diff --git a/pve-rs/src/sdn/mod.rs b/pve-rs/src/sdn/mod.rs index 3e3b1376f8d6..36afb099ece0 100644 --- a/pve-rs/src/sdn/mod.rs +++ b/pve-rs/src/sdn/mod.rs @@ -1 +1,2 @@ pub mod fabrics; +pub mod openfabric; diff --git a/pve-rs/src/sdn/openfabric.rs b/pve-rs/src/sdn/openfabric.rs new file mode 100644 index 000000000000..65d92d313b2a --- /dev/null +++ b/pve-rs/src/sdn/openfabric.rs @@ -0,0 +1,224 @@ +#[perlmod::package(name = "PVE::RS::SDN::Fabrics::OpenFabric", lib = "pve_rs")] +mod export { + use std::{collections::HashMap, fmt::Write, net::IpAddr, str, sync::Mutex}; + + use anyhow::{Context, Error}; + use perlmod::Value; + use proxmox_frr::serializer::to_raw_config; + use proxmox_network_types::{ + address::Cidr, + hostname::Hostname, + openfabric::{CsnpInterval, HelloInterval, HelloMultiplier}, + }; + use proxmox_schema::property_string::PropertyString; + use proxmox_section_config::typed::{ApiSectionDataEntry, SectionConfigData}; + use proxmox_ve_config::sdn::fabric::{ + FabricConfig, FrrConfigBuilder, Valid, Validate, + openfabric::{ + FabricId, FabricSection, InterfaceProperties, NodeId, NodeSection, + OpenFabricSectionConfig, + }, + }; + use serde::{Deserialize, Serialize}; + + use crate::sdn::fabrics::export::PerlSectionConfig; + + perlmod::declare_magic!(Box<PerlSectionConfig<OpenFabricSectionConfig>> : &PerlSectionConfig<OpenFabricSectionConfig> as "PVE::RS::SDN::Fabrics::OpenFabric"); + + #[derive(Debug, Serialize, Deserialize)] + pub struct AddFabric { + fabric_id: FabricId, + hello_interval: Option<HelloInterval>, + loopback_prefix: Cidr, + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct DeleteFabric { + fabric: FabricId, + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct DeleteNode { + fabric: FabricId, + node: Hostname, + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct DeleteInterface { + fabric: FabricId, + node: Hostname, + /// interface name + name: String, + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct EditFabric { + fabric: FabricId, + hello_interval: Option<HelloInterval>, + } + + #[derive(Debug, Deserialize)] + pub struct AddNode { + fabric: FabricId, + node: Hostname, + router_id: IpAddr, + interfaces: Vec<PropertyString<InterfaceProperties>>, + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct EditNode { + node: Hostname, + fabric: FabricId, + router_id: IpAddr, + interfaces: Vec<PropertyString<InterfaceProperties>>, + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct EditInterface { + node: Hostname, + fabric: FabricId, + name: String, + passive: bool, + hello_interval: Option<HelloInterval>, + hello_multiplier: Option<HelloMultiplier>, + csnp_interval: Option<CsnpInterval>, + } + + fn interface_exists( + config: &SectionConfigData<OpenFabricSectionConfig>, + interface_name: &str, + node_name: &str, + ) -> bool { + config.sections.iter().any(|(k, v)| { + if let OpenFabricSectionConfig::Node(n) = v { + k.parse::<NodeId>().ok().is_some_and(|id| { + id.node.as_ref() == node_name + && n.interface.iter().any(|i| i.name == interface_name) + }) + } else { + false + } + }) + } + + impl PerlSectionConfig<OpenFabricSectionConfig> { + pub fn add_fabric(&self, new_config: AddFabric) -> Result<(), anyhow::Error> { + let mut config = self.section_config.lock().unwrap(); + if config.sections.contains_key(new_config.fabric_id.as_ref()) { + anyhow::bail!("fabric already exists"); + } + let new_fabric = OpenFabricSectionConfig::Fabric(FabricSection { + fabric_id: new_config.fabric_id.clone(), + hello_interval: new_config.hello_interval, + ty: String::from("fabric"), + loopback_prefix: new_config.loopback_prefix, + }); + config + .sections + .insert(new_config.fabric_id.to_string(), new_fabric); + + config.order.push(new_config.fabric_id.to_string()); + OpenFabricSectionConfig::validate_as_ref(&config)?; + Ok(()) + } + + pub fn add_node(&self, new_config: AddNode) -> Result<(), anyhow::Error> { + let nodeid = NodeId::new(new_config.fabric, new_config.node); + let nodeid_key = nodeid.to_string(); + + let mut config = self.section_config.lock().unwrap(); + if config.sections.contains_key(&nodeid_key) { + anyhow::bail!("node already exists"); + } + if new_config + .interfaces + .iter() + .any(|i| interface_exists(&config, &i.name, nodeid.node.as_ref())) + { + anyhow::bail!("One interface cannot be a part of two fabrics"); + } + let new_fabric = OpenFabricSectionConfig::Node(NodeSection { + router_id: new_config.router_id, + interface: new_config.interfaces, + node_id: nodeid, + ty: String::from("node"), + }); + config.sections.insert(nodeid_key.clone(), new_fabric); + config.order.push(nodeid_key); + OpenFabricSectionConfig::validate_as_ref(&config)?; + + Ok(()) + } + + pub fn edit_fabric(&self, new_config: EditFabric) -> Result<(), anyhow::Error> { + let mut config = self.section_config.lock().unwrap(); + + let fabricid = new_config.fabric; + + if let OpenFabricSectionConfig::Fabric(fs) = config + .sections + .get_mut(fabricid.as_ref()) + .context("fabric doesn't exist")? + { + fs.hello_interval = new_config.hello_interval; + } + OpenFabricSectionConfig::validate_as_ref(&config)?; + Ok(()) + } + + pub fn edit_node(&self, new_config: EditNode) -> Result<(), anyhow::Error> { + let router_id = new_config.router_id; + + let nodeid = NodeId::new(new_config.fabric, new_config.node).to_string(); + + let mut config = self.section_config.lock().unwrap(); + if let Some(node) = config.sections.get_mut(&nodeid) { + if let OpenFabricSectionConfig::Node(n) = node { + n.router_id = router_id; + n.interface = new_config.interfaces; + } + } else { + anyhow::bail!("node not found"); + } + + OpenFabricSectionConfig::validate_as_ref(&config)?; + Ok(()) + } + + pub fn delete_fabric(&self, new_config: DeleteFabric) -> Result<(), anyhow::Error> { + let mut config = self.section_config.lock().unwrap(); + + let fabricid = new_config.fabric; + + config + .sections + .remove(fabricid.as_ref()) + .ok_or(anyhow::anyhow!("fabric not found"))?; + // remove all the nodes + config.sections.retain(|k, _v| { + if let Ok(nodeid) = k.parse::<NodeId>() { + return nodeid.fabric_id != fabricid; + } + true + }); + OpenFabricSectionConfig::validate_as_ref(&config)?; + Ok(()) + } + + pub fn delete_node(&self, new_config: DeleteNode) -> Result<(), anyhow::Error> { + let mut config = self.section_config.lock().unwrap(); + let nodeid = NodeId::new(new_config.fabric, new_config.node).to_string(); + config + .sections + .remove(&nodeid) + .ok_or(anyhow::anyhow!("node not found"))?; + OpenFabricSectionConfig::validate_as_ref(&config)?; + Ok(()) + } + + pub fn write(&self) -> Result<String, anyhow::Error> { + let guard = self.section_config.lock().unwrap().clone(); + OpenFabricSectionConfig::write_section_config("sdn/fabrics/openfabric.cfg", &guard) + } + } +} -- 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel