On Fri, Aug 22, 2025 at 11:00:37AM +0200, Gabriel Goller wrote: > Add a function that returns a list of all the routes which are > distributed using the fabrics. For this we again need to read the config > (in order to get the interface names and thus connect the fabric to the > discovered route) and we need to query frr (using vtysh) for all the > routes (ipv4 and ipv6) distributed by a specific protocol (once for > openfabric and once for ospf). This method is used in the > FabricContentView so that clicking on the fabric resource shows the > routes distributed by the fabric. > > Signed-off-by: Gabriel Goller <g.gol...@proxmox.com> > --- > pve-rs/src/bindings/sdn/fabrics.rs | 157 +++++++++++++++++++++++++++++ > 1 file changed, 157 insertions(+) > > diff --git a/pve-rs/src/bindings/sdn/fabrics.rs > b/pve-rs/src/bindings/sdn/fabrics.rs > index 3f70d421e582..f1addd4364d2 100644 > --- a/pve-rs/src/bindings/sdn/fabrics.rs > +++ b/pve-rs/src/bindings/sdn/fabrics.rs > @@ -594,6 +594,18 @@ pub mod pve_rs_sdn_fabrics { > section_config::{fabric::FabricId, node::Node as ConfigNode}, > }; > > + /// The status of a route. > + /// > + /// Contains the route, the fabric and protocol it belongs to and > some extra nexthop > + /// information. > + #[derive(Debug, Serialize)] > + pub struct RouteStatus { > + route: String, > + via: Vec<String>, > + fabric_id: FabricId, > + protocol: Protocol, > + } > + > /// Protocol > #[derive(Debug, Serialize, Clone, Copy)] > pub enum Protocol { > @@ -639,6 +651,94 @@ pub mod pve_rs_sdn_fabrics { > pub ospf: de::Routes, > } > > + impl TryInto<Vec<RouteStatus>> for RoutesParsed { > + type Error = anyhow::Error; > + > + fn try_into(self) -> Result<Vec<RouteStatus>, Self::Error> { > + let hostname = proxmox_sys::nodename(); > + > + // to associate a route to a fabric, we get all the > interfaces which are associated > + // with a fabric on this node and compare them with the > interfaces on the route. > + let raw_config = > std::fs::read_to_string("/etc/pve/sdn/fabrics.cfg")?;
^ like in patch 2 - file i/o in TryInto is awkward. > + let config = > FabricConfig::parse_section_config(&raw_config)?; > + > + let mut stats: Vec<RouteStatus> = Vec::new(); > + > + for (nodeid, node) in config.values().flat_map(|entry| { > + entry > + .nodes() > + .map(|(id, node)| (id.to_string(), node.clone())) ^ like in patch 2 > + }) { > + if nodeid != hostname { > + continue; > + } > + let fabric_id = node.id().fabric_id().clone(); ^ Unnecessary clone. > + > + let current_protocol = match &node { > + ConfigNode::Openfabric(_) => Protocol::Openfabric, > + ConfigNode::Ospf(_) => Protocol::Ospf, > + }; > + > + // get interfaces > + let interface_names: HashSet<String> = match node { ↕ like on patch 2 > + ConfigNode::Openfabric(n) => n > + .properties() > + .interfaces() > + .map(|i| i.name().to_string()) > + .collect(), > + ConfigNode::Ospf(n) => n > + .properties() > + .interfaces() > + .map(|i| i.name().to_string()) > + .collect(), > + }; > + > + let mut all_routes = HashMap::new(); ^ Unnecessary clone & mutability, since this is always exactly a clone of 1 HashMap: > + match current_protocol { > + Protocol::Openfabric => > all_routes.extend(&self.openfabric.0), > + Protocol::Ospf => all_routes.extend(&self.ospf.0), > + } let all_routes = match current_protocol { Protocol::Openfabric => &self.openfabric.0, Protocol::Ospf => &self.ospf.0, }; (Optioanlly combine it with the `let current_protocol` above into a let (current_protocol, all_routes) = match &node { ... => (Protocol::Foo, &self.foo.0), } ) > + > + for (route_key, route_list) in all_routes { > + let mut route_belongs_to_fabric = false; > + for route in route_list { > + for nexthop in &route.nexthops { > + if > interface_names.contains(&nexthop.interface_name) { > + 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 { > + nexthop.interface_name.clone() > + }; > + via_list.push(via); > + } > + } > + > + stats.push(RouteStatus { > + route: route_key.to_string(), > + via: via_list, > + protocol: current_protocol, > + fabric_id: fabric_id.clone(), > + }); > + } > + } > + } > + Ok(stats) > + } > + } > + > impl TryInto<HashMap<FabricId, Status>> for RoutesParsed { > type Error = anyhow::Error; > > @@ -716,6 +816,63 @@ pub mod pve_rs_sdn_fabrics { > } > } > > + /// Get all the routes for all the fabrics on this node. > + /// > + /// Use FRR to get all the routes that have been inserted by either > `openfabric` or 'ospf` and > + /// associate them with the respective fabric by checking the interface > they point to. Return a > + /// single array with all routes. > + #[export] > + fn routes() -> Result<Vec<status::RouteStatus>, Error> { > + 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 ospf_routes_string = String::from_utf8( > + Command::new("sh") > + .args(["-c", "vtysh -c 'show ip route ospf 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); > + } > + > + 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")? > + }; > + > + let route_status = status::RoutesParsed { > + openfabric: openfabric_routes, > + ospf: ospf_routes, > + }; > + > + route_status.try_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. > -- > 2.47.2 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel