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