From: Gabriel Goller <[email protected]> Add function to retrieve routes learned via OpenFabric or OSPF for a specific fabric. Query FRR using `show ip route <protocol>` commands so that we get a common json schema for every protocol. Match routes to the fabric by comparing outgoing interfaces against the fabric's configured interfaces on the local node.
Signed-off-by: Gabriel Goller <[email protected]> Signed-off-by: Stefan Hanreich <[email protected]> --- pve-rs/src/bindings/sdn/fabrics.rs | 61 +++++++++++++++++++ pve-rs/src/sdn/status.rs | 94 +++++++++++++++++++++++++++++- 2 files changed, 154 insertions(+), 1 deletion(-) diff --git a/pve-rs/src/bindings/sdn/fabrics.rs b/pve-rs/src/bindings/sdn/fabrics.rs index a1f056d..5fbb67e 100644 --- a/pve-rs/src/bindings/sdn/fabrics.rs +++ b/pve-rs/src/bindings/sdn/fabrics.rs @@ -605,6 +605,67 @@ pub mod pve_rs_sdn_fabrics { .with_context(|| "error converting section config to fabricconfig") } + /// Get the routes that have been learned and distributed by this specific fabric on this node. + /// + /// Read and parse the fabric config to get the protocol and the interfaces. Parse the vtysh + /// output and assign the routes to a fabric by using the interface list. Return a list of + /// common route structs. + #[export] + fn routes(fabric_id: FabricId) -> Result<Vec<status::RouteStatus>, 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_ipv4_routes_string = String::from_utf8( + Command::new("sh") + .args(["-c", "vtysh -c 'show ip route openfabric json'"]) + .output()? + .stdout, + )?; + + let openfabric_ipv6_routes_string = String::from_utf8( + Command::new("sh") + .args(["-c", "vtysh -c 'show ipv6 route openfabric json'"]) + .output()? + .stdout, + )?; + + let mut openfabric_routes: proxmox_frr::de::Routes = + if openfabric_ipv4_routes_string.is_empty() { + proxmox_frr::de::Routes::default() + } else { + serde_json::from_str(&openfabric_ipv4_routes_string) + .with_context(|| "error parsing openfabric ipv4 routes")? + }; + if !openfabric_ipv6_routes_string.is_empty() { + let openfabric_ipv6_routes: proxmox_frr::de::Routes = + serde_json::from_str(&openfabric_ipv6_routes_string) + .with_context(|| "error parsing openfabric ipv6 routes")?; + openfabric_routes.0.extend(openfabric_ipv6_routes.0); + } + status::get_routes(fabric_id, config, openfabric_routes) + } + FabricEntry::Ospf(_) => { + let ospf_routes_string = String::from_utf8( + Command::new("sh") + .args(["-c", "vtysh -c 'show ip route ospf json'"]) + .output()? + .stdout, + )?; + let ospf_routes: proxmox_frr::de::Routes = if ospf_routes_string.is_empty() { + proxmox_frr::de::Routes::default() + } else { + serde_json::from_str(&ospf_routes_string) + .with_context(|| "error parsing ospf routes")? + }; + + status::get_routes(fabric_id, config, ospf_routes) + } + } + } + /// 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 0c9dc0f..ba7fcf7 100644 --- a/pve-rs/src/sdn/status.rs +++ b/pve-rs/src/sdn/status.rs @@ -10,10 +10,19 @@ use proxmox_ve_config::{ common::valid::Valid, sdn::fabric::{ FabricConfig, - section_config::{Section, fabric::FabricId, node::Node as ConfigNode}, + section_config::{Section, fabric::FabricId, node::Node as ConfigNode, node::NodeId}, }, }; +/// The status of a route. +/// +/// Contains the route and all the nexthops. This is common across all protocols. +#[derive(Debug, Serialize)] +pub struct RouteStatus { + route: String, + via: Vec<String>, +} + /// Protocol #[derive(Debug, Serialize, Clone, Copy)] #[serde(rename_all = "lowercase")] @@ -73,6 +82,89 @@ pub struct FabricsRunningConfig { pub ids: BTreeMap<String, Section>, } +/// Converts the parsed `show ip route x` frr route output into a list of common [`RouteStatus`] +/// structs. +/// +/// We always execute `show ip route <protocol>` so we only get routes generated from a specific +/// protocol. The problem is that we can't definitely link a specific route to a specific fabric. +/// To solve this, we retrieve all the interfaces configured on a fabric on this node and check +/// which route contains a output interface of the fabric. +pub fn get_routes( + fabric_id: FabricId, + config: Valid<FabricConfig>, + routes: de::Routes, +) -> Result<Vec<RouteStatus>, anyhow::Error> { + let hostname = proxmox_sys::nodename(); + + let mut stats: Vec<RouteStatus> = Vec::new(); + + if let Ok(node) = config + .get_fabric(&fabric_id)? + .get_node(&NodeId::from_string(hostname.to_string())?) + { + let mut interface_names: HashSet<&str> = match node { + ConfigNode::Openfabric(n) => n + .properties() + .interfaces() + .map(|i| i.name().as_str()) + .collect(), + ConfigNode::Ospf(n) => n + .properties() + .interfaces() + .map(|i| i.name().as_str()) + .collect(), + }; + + let dummy_interface = format!("dummy_{}", fabric_id.as_str()); + interface_names.insert(&dummy_interface); + + for (route_key, route_list) in routes.0 { + let mut route_belongs_to_fabric = false; + for route in &route_list { + if !route.installed.unwrap_or_default() { + continue; + } + + for nexthop in &route.nexthops { + if let Some(iface_name) = &nexthop.interface_name { + if interface_names.contains(iface_name.as_str()) { + route_belongs_to_fabric = true; + break; + } + } + } + if route_belongs_to_fabric { + break; + } + } + + if route_belongs_to_fabric { + let mut via_list = Vec::new(); + for route in route_list { + for nexthop in &route.nexthops { + let via = if let Some(ip) = nexthop.ip { + ip.to_string() + } else if let Some(iface_name) = &nexthop.interface_name { + iface_name.clone() + } else if let Some(true) = &nexthop.unreachable { + "unreachable".to_string() + } else { + continue; + }; + via_list.push(via); + } + } + + stats.push(RouteStatus { + route: route_key.to_string(), + via: via_list, + }); + } + } + } + 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
