[pve-devel] [PATCH proxmox-perl-rs v2 3/4] fabrics: add function to get all routes distributed by the fabrics
Wolfgang Bumiller
w.bumiller at proxmox.com
Mon Aug 25 10:22:44 CEST 2025
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.goller at 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
More information about the pve-devel
mailing list