On Fri, Aug 22, 2025 at 11:00:38AM +0200, Gabriel Goller wrote:
> In order to also display the neighbors of a specific node in the
> FabricContentView resource window get the Neighbors of the all the
> fabrics. Query frr (vtysh) to get the neighbors of both openefabric and
> ospf, parse it and then compile a array containing all neighbors and
> the fabric it relates to.
> 
> Signed-off-by: Gabriel Goller <g.gol...@proxmox.com>
> ---
>  pve-rs/src/bindings/sdn/fabrics.rs | 152 +++++++++++++++++++++++++++++
>  1 file changed, 152 insertions(+)
> 
> diff --git a/pve-rs/src/bindings/sdn/fabrics.rs 
> b/pve-rs/src/bindings/sdn/fabrics.rs
> index f1addd4364d2..c033a4072685 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 neighbor.
> +        ///
> +        /// Contains the neighbor, the fabric and protocol it belongs to and 
> the some status
> +        /// information.
> +        #[derive(Debug, Serialize)]
> +        pub struct NeighborStatus {
> +            neighbor: String,
> +            status: String,
> +            fabric_id: FabricId,
> +            protocol: Protocol,
> +        }
> +
>          /// The status of a route.
>          ///
>          /// Contains the route, the fabric and protocol it belongs to and 
> some extra nexthop
> @@ -651,6 +663,19 @@ pub mod pve_rs_sdn_fabrics {
>              pub ospf: de::Routes,
>          }
>  
> +        /// Parsed neighbors for all protocols
> +        ///
> +        /// These are the neighbors parsed from the json output of:
> +        /// `vtysh -c 'show openfabric neighbor json'` and
> +        /// `vtysh -c 'show ip ospf neighbor json'`.
> +        #[derive(Debug, Serialize)]
> +        pub struct NeighborsParsed {
> +            /// The openfabric neighbors in FRR
> +            pub openfabric: de::openfabric::Neighbors,
> +            /// The ospf neighbors in FRR
> +            pub ospf: de::ospf::Neighbors,
> +        }
> +
>          impl TryInto<Vec<RouteStatus>> for RoutesParsed {
>              type Error = anyhow::Error;
>  
> @@ -739,6 +764,90 @@ pub mod pve_rs_sdn_fabrics {
>              }
>          }
>  
> +        impl TryInto<Vec<NeighborStatus>> for NeighborsParsed {
> +            type Error = anyhow::Error;
> +
> +            fn try_into(self) -> Result<Vec<NeighborStatus>, Self::Error> {
> +                let hostname = proxmox_sys::nodename();
> +
> +                // get all nodes
> +                let raw_config = 
> std::fs::read_to_string("/etc/pve/sdn/fabrics.cfg")?;

^ Same as the other patches.

> +                let config = 
> FabricConfig::parse_section_config(&raw_config)?;
> +
> +                let mut stats: Vec<NeighborStatus> = Vec::new();
> +
> +                for (nodeid, node) in config.values().flat_map(|entry| {
> +                    entry
> +                        .nodes()
> +                        .map(|(id, node)| (id.to_string(), node.clone()))

^ ...
I'm sensing pattern here.

Would it make sense to add a `FabricConfig::all_nodes(&self) -> impl 
Iterator<...>` ?

> +                }) {
> +                    if nodeid != hostname {
> +                        continue;
> +                    }
> +                    let fabric_id = node.id().fabric_id().clone();

^ unnecessary clone

> +
> +                    match node {
> +                        ConfigNode::Openfabric(_) => {
> +                            for area in &self.openfabric.areas {
> +                                if area.area == fabric_id.as_str() {
> +                                    for circuit in &area.circuits {
> +                                        if let (Some(adj), Some(state)) =
> +                                            (&circuit.adj, &circuit.state)
> +                                        {
> +                                            stats.push(NeighborStatus {
> +                                                neighbor: adj.clone(),
> +                                                status: state.clone(),
> +                                                protocol: 
> Protocol::Openfabric,
> +                                                fabric_id: fabric_id.clone(),
> +                                            });
> +                                        }
> +                                    }
> +                                }
> +                            }
> +                        }
> +                        ConfigNode::Ospf(node) => {
> +                            let interface_names: HashSet<&str> = node
> +                                .properties()
> +                                .interfaces()
> +                                .map(|i| i.name().as_str())
> +                                .collect();
> +
> +                            for (neighbor_key, neighbor_list) in 
> &self.ospf.neighbors {
> +                                let mut has_matching_neighbor = false;
> +                                for neighbor in neighbor_list {
> +                                    match 
> neighbor.interface_name.split_once(":") {
> +                                        Some((interface_name, _)) => {
> +                                            if 
> interface_names.contains(interface_name) {
> +                                                has_matching_neighbor = true;
> +                                                break;
> +                                            }
> +                                        }
> +                                        _ => {
> +                                            continue;
> +                                        }
> +                                    }
> +                                }
> +                                if has_matching_neighbor {
> +                                    let status = neighbor_list
> +                                        .first()
> +                                        .map(|n| n.neighbor_state.clone())
> +                                        .unwrap_or_default();
> +                                    stats.push(NeighborStatus {
> +                                        neighbor: neighbor_key.clone(),
> +                                        status,
> +                                        protocol: Protocol::Ospf,
> +                                        fabric_id: fabric_id.clone(),
> +                                    });
> +                                }
> +                            }
> +                        }
> +                    }
> +                }
> +
> +                Ok(stats)
> +            }
> +        }
> +
>          impl TryInto<HashMap<FabricId, Status>> for RoutesParsed {
>              type Error = anyhow::Error;
>  
> @@ -873,6 +982,49 @@ pub mod pve_rs_sdn_fabrics {
>          route_status.try_into()
>      }
>  
> +    /// Get all the neighbors of all the fabrics on this node.
> +    ///
> +    /// Go through all fabrics that exist on this node. Then get the 
> neighbors of them all and
> +    /// concat them into a single array.
> +    #[export]
> +    fn neighbors() -> Result<Vec<status::NeighborStatus>, Error> {
> +        let openfabric_neighbors_string = String::from_utf8(
> +            Command::new("sh")
> +                .args(["-c", "vtysh -c 'show openfabric neighbor json'"])
> +                .output()?
> +                .stdout,
> +        )?;
> +
> +        let ospf_neighbors_string = String::from_utf8(
> +            Command::new("sh")
> +                .args(["-c", "vtysh -c 'show ip ospf neighbor json'"])
> +                .output()?
> +                .stdout,
> +        )?;
> +
> +        let openfabric_neighbors: proxmox_frr::de::openfabric::Neighbors =
> +            if openfabric_neighbors_string.is_empty() {
> +                proxmox_frr::de::openfabric::Neighbors::default()
> +            } else {
> +                serde_json::from_str(&openfabric_neighbors_string)
> +                    .with_context(|| "error parsing openfabric neighbors")?
> +            };
> +
> +        let ospf_neighbors: proxmox_frr::de::ospf::Neighbors = if 
> ospf_neighbors_string.is_empty() {
> +            proxmox_frr::de::ospf::Neighbors::default()
> +        } else {
> +            serde_json::from_str(&ospf_neighbors_string)
> +                .with_context(|| "error parsing ospf neighbors")?
> +        };
> +
> +        let neighbor_status = status::NeighborsParsed {
> +            openfabric: openfabric_neighbors,
> +            ospf: ospf_neighbors,
> +        };
> +
> +        neighbor_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

Reply via email to