From: Gabriel Goller <[email protected]> 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 <[email protected]> Signed-off-by: Stefan Hanreich <[email protected]> --- 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<status::InterfaceStatus, Error> { + // 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<openfabric::InterfaceStatus>), + Ospf(Vec<ospf::InterfaceStatus>), +} + +impl From<Vec<openfabric::InterfaceStatus>> for InterfaceStatus { + fn from(value: Vec<openfabric::InterfaceStatus>) -> Self { + InterfaceStatus::Openfabric(value) + } +} +impl From<Vec<ospf::InterfaceStatus>> for InterfaceStatus { + fn from(value: Vec<ospf::InterfaceStatus>) -> 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<Vec<openfabric::InterfaceStatus>, anyhow::Error> { + let mut stats: Vec<openfabric::InterfaceStatus> = 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<OspfProperties, OspfNodeProperties>, + neighbors: de::ospf::Interfaces, +) -> Result<Vec<ospf::InterfaceStatus>, anyhow::Error> { + let hostname = proxmox_sys::nodename(); + + let mut stats: Vec<ospf::InterfaceStatus> = 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 [email protected] https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
