From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 87BBB1FF17E for ; Thu, 30 Oct 2025 16:49:21 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 4D9B327112; Thu, 30 Oct 2025 16:49:07 +0100 (CET) From: Stefan Hanreich To: pve-devel@lists.proxmox.com Date: Thu, 30 Oct 2025 16:48:24 +0100 Message-ID: <20251030154851.540408-18-s.hanreich@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20251030154851.540408-1-s.hanreich@proxmox.com> References: <20251030154851.540408-1-s.hanreich@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.184 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 KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods RDNS_NONE 0.793 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record Subject: [pve-devel] [PATCH proxmox-perl-rs 09/10] pve-rs: fabrics: add function to get the interfaces used for a fabric X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox VE development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" From: Gabriel Goller Add a function which queries FRR to return all the interfaces which are used on the local node by a specific fabric. Again, we can associate a fabric with a specific openfabric/ospf interface by reading and parsing the fabric config and getting all the interfaces configured on the node and matching them to the FRR output. Signed-off-by: Gabriel Goller Signed-off-by: Stefan Hanreich --- pve-rs/src/bindings/sdn/fabrics.rs | 50 +++++++++++ pve-rs/src/sdn/status.rs | 133 ++++++++++++++++++++++++++++- 2 files changed, 182 insertions(+), 1 deletion(-) diff --git a/pve-rs/src/bindings/sdn/fabrics.rs b/pve-rs/src/bindings/sdn/fabrics.rs index 5fbb67e..150b7fa 100644 --- a/pve-rs/src/bindings/sdn/fabrics.rs +++ b/pve-rs/src/bindings/sdn/fabrics.rs @@ -666,6 +666,56 @@ pub mod pve_rs_sdn_fabrics { } } + /// Get the interfaces for this specific fabric on this node + /// + /// Read and parse the fabric config to get the protocol of the fabric and retrieve the + /// interfaces (ospf). Convert the frr output into a common format of fabric interfaces. + #[export] + fn interfaces(fabric_id: FabricId) -> Result { + // Read fabric config to get protocol of fabric + let config = get_fabrics_config()?; + + let fabric = config.get_fabric(&fabric_id)?; + + match fabric { + FabricEntry::Openfabric(_) => { + let openfabric_interface_string = String::from_utf8( + Command::new("sh") + .args(["-c", "vtysh -c 'show openfabric interface json'"]) + .output()? + .stdout, + )?; + let openfabric_interfaces: proxmox_frr::de::openfabric::Interfaces = + if openfabric_interface_string.is_empty() { + proxmox_frr::de::openfabric::Interfaces::default() + } else { + serde_json::from_str(&openfabric_interface_string) + .with_context(|| "error parsing openfabric interfaces")? + }; + + status::get_interfaces_openfabric(fabric_id, openfabric_interfaces) + .map(|v| v.into()) + } + FabricEntry::Ospf(fabric) => { + let ospf_interfaces_string = String::from_utf8( + Command::new("sh") + .args(["-c", "vtysh -c 'show ip ospf interface json'"]) + .output()? + .stdout, + )?; + let ospf_interfaces: proxmox_frr::de::ospf::Interfaces = + if ospf_interfaces_string.is_empty() { + proxmox_frr::de::ospf::Interfaces::default() + } else { + serde_json::from_str(&ospf_interfaces_string) + .with_context(|| "error parsing ospf interfaces")? + }; + + status::get_interfaces_ospf(fabric_id, fabric, ospf_interfaces).map(|v| v.into()) + } + } + } + /// Return the status of all fabrics on this node. /// /// Go through all fabrics in the config, then filter out the ones that exist on this node. diff --git a/pve-rs/src/sdn/status.rs b/pve-rs/src/sdn/status.rs index ba7fcf7..450bb6c 100644 --- a/pve-rs/src/sdn/status.rs +++ b/pve-rs/src/sdn/status.rs @@ -6,14 +6,79 @@ use proxmox_network_types::mac_address::MacAddress; use serde::{Deserialize, Serialize}; use proxmox_frr::de::{self}; +use proxmox_ve_config::sdn::fabric::section_config::protocol::ospf::{ + OspfNodeProperties, OspfProperties, +}; use proxmox_ve_config::{ common::valid::Valid, sdn::fabric::{ - FabricConfig, + Entry, FabricConfig, section_config::{Section, fabric::FabricId, node::Node as ConfigNode, node::NodeId}, }, }; +// The status of a fabric interface +// +// Either up or down. +#[derive(Debug, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum InterfaceState { + Up, + Down, +} + +mod ospf { + use proxmox_frr::de; + use serde::Serialize; + + /// The status of a fabric interface + /// + /// Contains the interface name, the interface state (so if the interface is up/down) and the type + /// of the interface (e.g. point-to-point, broadcast, etc.). + #[derive(Debug, Serialize)] + pub struct InterfaceStatus { + pub name: String, + pub state: super::InterfaceState, + #[serde(rename = "type")] + pub ty: de::ospf::NetworkType, + } +} +mod openfabric { + use proxmox_frr::de; + use serde::Serialize; + + /// The status of a fabric interface + /// + /// Contains the interface name, the interface state (so if the interface is up/down) and the type + /// of the interface (e.g. point-to-point, broadcast, etc.). + #[derive(Debug, Serialize)] + pub struct InterfaceStatus { + pub name: String, + pub state: de::openfabric::CircuitState, + #[serde(rename = "type")] + pub ty: de::openfabric::NetworkType, + } +} + +/// Common InterfaceStatus that contains either OSPF or Openfabric interfaces +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub enum InterfaceStatus { + Openfabric(Vec), + Ospf(Vec), +} + +impl From> for InterfaceStatus { + fn from(value: Vec) -> Self { + InterfaceStatus::Openfabric(value) + } +} +impl From> for InterfaceStatus { + fn from(value: Vec) -> Self { + InterfaceStatus::Ospf(value) + } +} + /// The status of a route. /// /// Contains the route and all the nexthops. This is common across all protocols. @@ -165,6 +230,72 @@ pub fn get_routes( Ok(stats) } +/// Conver the `show openfabric interface` output into a list of [`openfabric::InterfaceStatus`]. +/// +/// Openfabric uses the name of the fabric as an "area", so simply match that to the fabric_id. +pub fn get_interfaces_openfabric( + fabric_id: FabricId, + interfaces: de::openfabric::Interfaces, +) -> Result, anyhow::Error> { + let mut stats: Vec = Vec::new(); + + for area in &interfaces.areas { + if area.area == fabric_id.as_str() { + for circuit in &area.circuits { + stats.push(openfabric::InterfaceStatus { + name: circuit.interface.name.clone(), + state: circuit.interface.state, + ty: circuit.interface.ty, + }); + } + } + } + + Ok(stats) +} + +/// Convert the `show ip ospf interface` output into a list of [`ospf::InterfaceStatus`]. +/// +/// Ospf does not use the name of the fabric at all, so we again need to retrieve the interfaces of +/// the fabric on this specific node and then match the interfaces to the fabric using the +/// interface names. +pub fn get_interfaces_ospf( + fabric_id: FabricId, + fabric: &Entry, + neighbors: de::ospf::Interfaces, +) -> Result, anyhow::Error> { + let hostname = proxmox_sys::nodename(); + + let mut stats: Vec = Vec::new(); + + if let Ok(node) = fabric.node_section(&NodeId::from_string(hostname.to_string())?) { + let mut fabric_interface_names: HashSet<&str> = node + .properties() + .interfaces() + .map(|i| i.name().as_str()) + .collect(); + + let dummy_interface = format!("dummy_{}", fabric_id.as_str()); + fabric_interface_names.insert(&dummy_interface); + + for (interface_name, interface) in &neighbors.interfaces { + if fabric_interface_names.contains(interface_name.as_str()) { + stats.push(ospf::InterfaceStatus { + name: interface_name.to_string(), + state: if interface.if_up { + InterfaceState::Up + } else { + InterfaceState::Down + }, + ty: interface.network_type, + }); + } + } + } + + Ok(stats) +} + /// Get the status for each fabric using the parsed routes from frr /// /// Using the parsed routes we get from frr, filter and map them to a HashMap mapping every -- 2.47.3 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel