Add CRUD functions for managing OSPF fabrics. Signed-off-by: Gabriel Goller <g.gol...@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