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 [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 168C81FF164 for <inbox@lore.proxmox.com>; Fri, 28 Mar 2025 18:15:45 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 4026688D0; Fri, 28 Mar 2025 18:14:07 +0100 (CET) From: Gabriel Goller <g.goller@proxmox.com> To: pve-devel@lists.proxmox.com Date: Fri, 28 Mar 2025 18:13:12 +0100 Message-Id: <20250328171340.885413-25-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 5/7] perl-rs: sdn: add CRUD helpers for OSPF 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 CRUD functions for managing OSPF fabrics. Signed-off-by: Gabriel Goller <g.goller@proxmox.com> --- pve-rs/Makefile | 1 + pve-rs/src/sdn/mod.rs | 1 + pve-rs/src/sdn/ospf.rs | 208 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+) create mode 100644 pve-rs/src/sdn/ospf.rs diff --git a/pve-rs/Makefile b/pve-rs/Makefile index 6bd9c8a2acec..5bd4d3c58b36 100644 --- a/pve-rs/Makefile +++ b/pve-rs/Makefile @@ -33,6 +33,7 @@ PERLMOD_PACKAGES := \ PVE::RS::ResourceScheduling::Static \ PVE::RS::SDN::Fabrics \ PVE::RS::SDN::Fabrics::OpenFabric \ + PVE::RS::SDN::Fabrics::Ospf \ 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 36afb099ece0..6700c989483f 100644 --- a/pve-rs/src/sdn/mod.rs +++ b/pve-rs/src/sdn/mod.rs @@ -1,2 +1,3 @@ pub mod fabrics; pub mod openfabric; +pub mod ospf; diff --git a/pve-rs/src/sdn/ospf.rs b/pve-rs/src/sdn/ospf.rs new file mode 100644 index 000000000000..f6aac0db83f1 --- /dev/null +++ b/pve-rs/src/sdn/ospf.rs @@ -0,0 +1,208 @@ +#[perlmod::package(name = "PVE::RS::SDN::Fabrics::Ospf", lib = "pve_rs")] +mod export { + use std::{collections::HashMap, fmt::Write, net::Ipv4Addr, str, sync::Mutex}; + + use anyhow::{Context, Error}; + use perlmod::Value; + use proxmox_frr::serializer::to_raw_config; + use proxmox_network_types::{ + address::Ipv4Cidr, + hostname::Hostname, + }; + use proxmox_schema::property_string::PropertyString; + use proxmox_section_config::typed::{ApiSectionDataEntry, SectionConfigData}; + use proxmox_ve_config::sdn::fabric::{ + FabricConfig, FrrConfigBuilder, Valid, Validate as _, + ospf::{Area, FabricSection, InterfaceProperties, NodeId, NodeSection, OspfSectionConfig}, + }; + use serde::{Deserialize, Serialize}; + + use crate::sdn::fabrics::export::PerlSectionConfig; + + perlmod::declare_magic!(Box<PerlSectionConfig<OspfSectionConfig>> : &PerlSectionConfig<OspfSectionConfig> as "PVE::RS::SDN::Fabrics::Ospf"); + + #[derive(Debug, Serialize, Deserialize)] + pub struct AddFabric { + area: Area, + loopback_prefix: Ipv4Cidr, + } + + #[derive(Debug, Deserialize)] + pub struct AddNode { + node: Hostname, + fabric: Area, + router_id: Ipv4Addr, + interfaces: Vec<PropertyString<InterfaceProperties>>, + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct DeleteFabric { + fabric: Area, + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct DeleteNode { + fabric: Area, + node: Hostname, + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct DeleteInterface { + fabric: Area, + node: Hostname, + /// interface name + name: String, + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct EditFabric { + name: String, + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct EditNode { + fabric: Area, + node: Hostname, + + router_id: Ipv4Addr, + interfaces: Vec<PropertyString<InterfaceProperties>>, + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct EditInterface { + fabric: Area, + node: Hostname, + name: String, + + passive: bool, + } + + fn interface_exists( + config: &SectionConfigData<OspfSectionConfig>, + interface_name: &str, + node_name: &str, + ) -> bool { + config.sections.iter().any(|(k, v)| { + if let OspfSectionConfig::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<OspfSectionConfig> { + pub fn add_fabric(&self, new_config: AddFabric) -> Result<(), anyhow::Error> { + let area = new_config.area.to_string(); + let mut config = self.section_config.lock().unwrap(); + if config.sections.contains_key(&area) { + anyhow::bail!("fabric already exists"); + } + let new_fabric = OspfSectionConfig::Fabric(FabricSection { + area: new_config.area, + ty: String::from("fabric"), + loopback_prefix: new_config.loopback_prefix, + }); + config.sections.insert(area.clone(), new_fabric); + config.order.push(area); + OspfSectionConfig::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 areas"); + } + + let new_fabric = OspfSectionConfig::Node(NodeSection { + node_id: nodeid, + router_id: new_config.router_id, + interface: new_config.interfaces, + ty: String::from("node"), + }); + config.sections.insert(nodeid_key.clone(), new_fabric); + config.order.push(nodeid_key); + OspfSectionConfig::validate_as_ref(&config)?; + Ok(()) + } + + pub fn edit_fabric(&self, new_config: EditFabric) -> Result<(), anyhow::Error> { + let mut config = self.section_config.lock().unwrap(); + + if let OspfSectionConfig::Fabric(_fs) = config + .sections + .get_mut(&new_config.name) + .context("fabric doesn't exist")? + { + // currently no properties exist here + } + OspfSectionConfig::validate_as_ref(&config)?; + Ok(()) + } + + pub fn edit_node(&self, new_config: EditNode) -> Result<(), anyhow::Error> { + 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 OspfSectionConfig::Node(n) = node { + n.router_id = new_config.router_id; + n.interface = new_config.interfaces; + } + } else { + anyhow::bail!("node not found"); + } + OspfSectionConfig::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 area = new_config.fabric; + config + .sections + .remove(area.as_ref()) + .ok_or(anyhow::anyhow!("no fabric found"))?; + + // remove all the nodes + config.sections.retain(|k, _v| { + if let Ok(nodeid) = k.parse::<NodeId>() { + return nodeid.area != area; + } + true + }); + OspfSectionConfig::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"))?; + OspfSectionConfig::validate_as_ref(&config)?; + Ok(()) + } + + pub fn write(&self) -> Result<String, anyhow::Error> { + let guard = self.section_config.lock().unwrap().clone(); + OspfSectionConfig::write_section_config("sdn/fabrics/ospf.cfg", &guard) + } + } +} -- 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel