[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