* [pve-devel] [PATCH manager/network/proxmox{-ve-rs, -perl-rs} v2 00/12] Add fabric status view
@ 2025-08-22 9:00 Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-ve-rs v2 1/2] frr: make room for deserialization structs Gabriel Goller
` (12 more replies)
0 siblings, 13 replies; 25+ messages in thread
From: Gabriel Goller @ 2025-08-22 9:00 UTC (permalink / raw)
To: pve-devel
This patch series adds fabric status monitoring to the UI by adding the fabric
status into pvestatd, making it visible in the SDN view and node resources.
There is also a new FabricContentView visible when clicking on the fabrics in
the node resource tree. There you can see the routes distributed by the fabric
and the neighbors of the current node. These statistics are retrieved using
separate api calls and are not stored in pvestatd.
The fabric status is "ok" when at least one route exists.
Route and neighbor data comes from FRR via `vtysh` commands. Since routes and
neighbors often lack fabric association, we match them by the interface name:
we retrieve interfaces configured for the fabric on the current node, then
match against e.g. a routes outgoing interface.
Changelog:
v1, thanks @Stefan:
* moved the frr deserialization structs to proxmox-frr
* fixed typo in documentation
proxmox-ve-rs:
Gabriel Goller (2):
frr: make room for deserialization structs
frr: add deserialization types for openfabric and ospf
proxmox-frr/Cargo.toml | 1 +
proxmox-frr/debian/control | 4 +
proxmox-frr/src/de/mod.rs | 104 ++++++++++
proxmox-frr/src/de/openfabric.rs | 42 ++++
proxmox-frr/src/de/ospf.rs | 57 ++++++
proxmox-frr/src/lib.rs | 243 +-----------------------
proxmox-frr/src/ser/mod.rs | 241 +++++++++++++++++++++++
proxmox-frr/src/{ => ser}/openfabric.rs | 4 +-
proxmox-frr/src/{ => ser}/ospf.rs | 2 +-
proxmox-frr/src/{ => ser}/route_map.rs | 0
proxmox-frr/src/{ => ser}/serializer.rs | 2 +-
proxmox-ve-config/src/sdn/fabric/frr.rs | 170 +++++++++--------
proxmox-ve-config/src/sdn/frr.rs | 2 +-
proxmox-ve-config/tests/fabric/main.rs | 2 +-
14 files changed, 549 insertions(+), 325 deletions(-)
create mode 100644 proxmox-frr/src/de/mod.rs
create mode 100644 proxmox-frr/src/de/openfabric.rs
create mode 100644 proxmox-frr/src/de/ospf.rs
create mode 100644 proxmox-frr/src/ser/mod.rs
rename proxmox-frr/src/{ => ser}/openfabric.rs (97%)
rename proxmox-frr/src/{ => ser}/ospf.rs (99%)
rename proxmox-frr/src/{ => ser}/route_map.rs (100%)
rename proxmox-frr/src/{ => ser}/serializer.rs (99%)
proxmox-perl-rs:
Gabriel Goller (4):
pve: fabrics: update proxmox-frr import path
fabrics: add function to get status of fabric
fabrics: add function to get all routes distributed by the fabrics
fabrics: add function to get all neighbors of the fabric
pve-rs/src/bindings/sdn/fabrics.rs | 505 ++++++++++++++++++++++++++++-
1 file changed, 504 insertions(+), 1 deletion(-)
pve-network:
Gabriel Goller (3):
fabrics: add fabrics status to SDN::status function
fabrics: add api endpoint to return fabrics routes
fabrics: add api endpoint to return fabric neighbors
src/PVE/API2/Network/SDN/Fabrics.pm | 117 +++++++++++++++++++++-
src/PVE/API2/Network/SDN/Zones/Content.pm | 2 +-
src/PVE/API2/Network/SDN/Zones/Status.pm | 2 +-
src/PVE/Network/SDN.pm | 6 +-
src/test/debug/statuscheck.pl | 3 +-
5 files changed, 124 insertions(+), 6 deletions(-)
pve-manager:
Gabriel Goller (3):
pvestatd: add fabrics status to pvestatd
fabrics: add resource view for fabrics
permissions: differentiate between zone and fabric paths
PVE/API2/Cluster.pm | 73 ++++++++++++---
PVE/Service/pvestatd.pm | 12 ++-
www/manager6/Makefile | 1 +
www/manager6/data/PermPathStore.js | 9 +-
www/manager6/sdn/Browser.js | 120 ++++++++++++++++++++-----
www/manager6/sdn/FabricsContentView.js | 91 +++++++++++++++++++
www/manager6/sdn/StatusView.js | 2 +-
7 files changed, 267 insertions(+), 41 deletions(-)
create mode 100644 www/manager6/sdn/FabricsContentView.js
Summary over all repositories:
27 files changed, 1444 insertions(+), 373 deletions(-)
--
Generated by git-murpp 0.8.0
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
* [pve-devel] [PATCH proxmox-ve-rs v2 1/2] frr: make room for deserialization structs
2025-08-22 9:00 [pve-devel] [PATCH manager/network/proxmox{-ve-rs, -perl-rs} v2 00/12] Add fabric status view Gabriel Goller
@ 2025-08-22 9:00 ` Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-ve-rs v2 2/2] frr: add deserialization types for openfabric and ospf Gabriel Goller
` (11 subsequent siblings)
12 siblings, 0 replies; 25+ messages in thread
From: Gabriel Goller @ 2025-08-22 9:00 UTC (permalink / raw)
To: pve-devel
Move all the serialization structs to a new subfolder `ser`. This makes
place for the deserialization structs which will land in a new folder
`de`. The deserialization structs will be used to parse the output of
`vtysh` commands, so that we can show statistics of various sdn objects.
Also update all the callsites to use the new subfolder.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
proxmox-frr/src/lib.rs | 242 +-----------------------
proxmox-frr/src/ser/mod.rs | 241 +++++++++++++++++++++++
proxmox-frr/src/{ => ser}/openfabric.rs | 4 +-
proxmox-frr/src/{ => ser}/ospf.rs | 2 +-
proxmox-frr/src/{ => ser}/route_map.rs | 0
proxmox-frr/src/{ => ser}/serializer.rs | 2 +-
proxmox-ve-config/src/sdn/fabric/frr.rs | 170 +++++++++--------
proxmox-ve-config/src/sdn/frr.rs | 2 +-
proxmox-ve-config/tests/fabric/main.rs | 2 +-
9 files changed, 340 insertions(+), 325 deletions(-)
create mode 100644 proxmox-frr/src/ser/mod.rs
rename proxmox-frr/src/{ => ser}/openfabric.rs (97%)
rename proxmox-frr/src/{ => ser}/ospf.rs (99%)
rename proxmox-frr/src/{ => ser}/route_map.rs (100%)
rename proxmox-frr/src/{ => ser}/serializer.rs (99%)
diff --git a/proxmox-frr/src/lib.rs b/proxmox-frr/src/lib.rs
index 86101182fafd..35b62cb39c91 100644
--- a/proxmox-frr/src/lib.rs
+++ b/proxmox-frr/src/lib.rs
@@ -1,241 +1 @@
-pub mod openfabric;
-pub mod ospf;
-pub mod route_map;
-pub mod serializer;
-
-use std::collections::{BTreeMap, BTreeSet};
-use std::fmt::Display;
-use std::str::FromStr;
-
-use crate::route_map::{AccessList, ProtocolRouteMap, RouteMap};
-
-use thiserror::Error;
-
-/// Generic FRR router.
-///
-/// This generic FRR router contains all the protocols that we implement.
-/// In FRR this is e.g.:
-/// ```text
-/// router openfabric test
-/// !....
-/// ! or
-/// router ospf
-/// !....
-/// ```
-#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
-pub enum Router {
- Openfabric(openfabric::OpenfabricRouter),
- Ospf(ospf::OspfRouter),
-}
-
-impl From<openfabric::OpenfabricRouter> for Router {
- fn from(value: openfabric::OpenfabricRouter) -> Self {
- Router::Openfabric(value)
- }
-}
-
-/// Generic FRR routername.
-///
-/// The variants represent different protocols. Some have `router <protocol> <name>`, others have
-/// `router <protocol> <process-id>`, some only have `router <protocol>`.
-#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
-pub enum RouterName {
- Openfabric(openfabric::OpenfabricRouterName),
- Ospf(ospf::OspfRouterName),
-}
-
-impl From<openfabric::OpenfabricRouterName> for RouterName {
- fn from(value: openfabric::OpenfabricRouterName) -> Self {
- Self::Openfabric(value)
- }
-}
-
-impl Display for RouterName {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- Self::Openfabric(r) => r.fmt(f),
- Self::Ospf(r) => r.fmt(f),
- }
- }
-}
-
-/// The interface name is the same on ospf and openfabric, but it is an enum so that we can have
-/// two different entries in the btreemap. This allows us to have an interface in a ospf and
-/// openfabric fabric.
-#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
-pub enum InterfaceName {
- Openfabric(CommonInterfaceName),
- Ospf(CommonInterfaceName),
-}
-
-impl Display for InterfaceName {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- InterfaceName::Openfabric(frr_word) => frr_word.fmt(f),
- InterfaceName::Ospf(frr_word) => frr_word.fmt(f),
- }
- }
-}
-
-/// Generic FRR Interface.
-///
-/// In FRR config it looks like this:
-/// ```text
-/// interface <name>
-/// ! ...
-#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub enum Interface {
- Openfabric(openfabric::OpenfabricInterface),
- Ospf(ospf::OspfInterface),
-}
-
-impl From<openfabric::OpenfabricInterface> for Interface {
- fn from(value: openfabric::OpenfabricInterface) -> Self {
- Self::Openfabric(value)
- }
-}
-
-impl From<ospf::OspfInterface> for Interface {
- fn from(value: ospf::OspfInterface) -> Self {
- Self::Ospf(value)
- }
-}
-
-#[derive(Error, Debug)]
-pub enum FrrWordError {
- #[error("word is empty")]
- IsEmpty,
- #[error("word contains invalid character")]
- InvalidCharacter,
-}
-
-/// A simple FRR Word.
-///
-/// Every string argument or value in FRR is an FrrWord. FrrWords must only contain ascii
-/// characters and must not have a whitespace.
-#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
-pub struct FrrWord(String);
-
-impl FrrWord {
- pub fn new<T: AsRef<str> + Into<String>>(name: T) -> Result<Self, FrrWordError> {
- if name.as_ref().is_empty() {
- return Err(FrrWordError::IsEmpty);
- }
-
- if name
- .as_ref()
- .as_bytes()
- .iter()
- .any(|c| !c.is_ascii() || c.is_ascii_whitespace())
- {
- eprintln!("invalid char in: \"{}\"", name.as_ref());
- return Err(FrrWordError::InvalidCharacter);
- }
-
- Ok(Self(name.into()))
- }
-}
-
-impl FromStr for FrrWord {
- type Err = FrrWordError;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- Self::new(s)
- }
-}
-
-impl Display for FrrWord {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- self.0.fmt(f)
- }
-}
-
-impl AsRef<str> for FrrWord {
- fn as_ref(&self) -> &str {
- &self.0
- }
-}
-
-#[derive(Error, Debug)]
-pub enum CommonInterfaceNameError {
- #[error("interface name too long")]
- TooLong,
-}
-
-/// Name of a interface, which is common between all protocols.
-///
-/// FRR itself doesn't enforce any limits, but the kernel does. Linux only allows interface names
-/// to be a maximum of 16 bytes. This is enforced by this struct.
-#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
-pub struct CommonInterfaceName(String);
-
-impl TryFrom<&str> for CommonInterfaceName {
- type Error = CommonInterfaceNameError;
-
- fn try_from(value: &str) -> Result<Self, Self::Error> {
- Self::new(value)
- }
-}
-
-impl TryFrom<String> for CommonInterfaceName {
- type Error = CommonInterfaceNameError;
-
- fn try_from(value: String) -> Result<Self, Self::Error> {
- Self::new(value)
- }
-}
-
-impl CommonInterfaceName {
- pub fn new<T: AsRef<str> + Into<String>>(s: T) -> Result<Self, CommonInterfaceNameError> {
- if s.as_ref().len() <= 15 {
- Ok(Self(s.into()))
- } else {
- Err(CommonInterfaceNameError::TooLong)
- }
- }
-}
-
-impl Display for CommonInterfaceName {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- self.0.fmt(f)
- }
-}
-
-/// Main FRR config.
-///
-/// Contains the two main frr building blocks: routers and interfaces. It also holds other
-/// top-level FRR options, such as access-lists, router-maps and protocol-routemaps. This struct
-/// gets generated using the `FrrConfigBuilder` in `proxmox-ve-config`.
-#[derive(Clone, Debug, PartialEq, Eq, Default)]
-pub struct FrrConfig {
- pub router: BTreeMap<RouterName, Router>,
- pub interfaces: BTreeMap<InterfaceName, Interface>,
- pub access_lists: Vec<AccessList>,
- pub routemaps: Vec<RouteMap>,
- pub protocol_routemaps: BTreeSet<ProtocolRouteMap>,
-}
-
-impl FrrConfig {
- pub fn new() -> Self {
- Self::default()
- }
-
- pub fn router(&self) -> impl Iterator<Item = (&RouterName, &Router)> + '_ {
- self.router.iter()
- }
-
- pub fn interfaces(&self) -> impl Iterator<Item = (&InterfaceName, &Interface)> + '_ {
- self.interfaces.iter()
- }
-
- pub fn access_lists(&self) -> impl Iterator<Item = &AccessList> + '_ {
- self.access_lists.iter()
- }
- pub fn routemaps(&self) -> impl Iterator<Item = &RouteMap> + '_ {
- self.routemaps.iter()
- }
-
- pub fn protocol_routemaps(&self) -> impl Iterator<Item = &ProtocolRouteMap> + '_ {
- self.protocol_routemaps.iter()
- }
-}
+pub mod ser;
diff --git a/proxmox-frr/src/ser/mod.rs b/proxmox-frr/src/ser/mod.rs
new file mode 100644
index 000000000000..a90397b59a9b
--- /dev/null
+++ b/proxmox-frr/src/ser/mod.rs
@@ -0,0 +1,241 @@
+pub mod openfabric;
+pub mod ospf;
+pub mod route_map;
+pub mod serializer;
+
+use std::collections::{BTreeMap, BTreeSet};
+use std::fmt::Display;
+use std::str::FromStr;
+
+use crate::ser::route_map::{AccessList, ProtocolRouteMap, RouteMap};
+
+use thiserror::Error;
+
+/// Generic FRR router.
+///
+/// This generic FRR router contains all the protocols that we implement.
+/// In FRR this is e.g.:
+/// ```text
+/// router openfabric test
+/// !....
+/// ! or
+/// router ospf
+/// !....
+/// ```
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub enum Router {
+ Openfabric(openfabric::OpenfabricRouter),
+ Ospf(ospf::OspfRouter),
+}
+
+impl From<openfabric::OpenfabricRouter> for Router {
+ fn from(value: openfabric::OpenfabricRouter) -> Self {
+ Router::Openfabric(value)
+ }
+}
+
+/// Generic FRR routername.
+///
+/// The variants represent different protocols. Some have `router <protocol> <name>`, others have
+/// `router <protocol> <process-id>`, some only have `router <protocol>`.
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub enum RouterName {
+ Openfabric(openfabric::OpenfabricRouterName),
+ Ospf(ospf::OspfRouterName),
+}
+
+impl From<openfabric::OpenfabricRouterName> for RouterName {
+ fn from(value: openfabric::OpenfabricRouterName) -> Self {
+ Self::Openfabric(value)
+ }
+}
+
+impl Display for RouterName {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Openfabric(r) => r.fmt(f),
+ Self::Ospf(r) => r.fmt(f),
+ }
+ }
+}
+
+/// The interface name is the same on ospf and openfabric, but it is an enum so that we can have
+/// two different entries in the btreemap. This allows us to have an interface in a ospf and
+/// openfabric fabric.
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub enum InterfaceName {
+ Openfabric(CommonInterfaceName),
+ Ospf(CommonInterfaceName),
+}
+
+impl Display for InterfaceName {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ InterfaceName::Openfabric(frr_word) => frr_word.fmt(f),
+ InterfaceName::Ospf(frr_word) => frr_word.fmt(f),
+ }
+ }
+}
+
+/// Generic FRR Interface.
+///
+/// In FRR config it looks like this:
+/// ```text
+/// interface <name>
+/// ! ...
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub enum Interface {
+ Openfabric(openfabric::OpenfabricInterface),
+ Ospf(ospf::OspfInterface),
+}
+
+impl From<openfabric::OpenfabricInterface> for Interface {
+ fn from(value: openfabric::OpenfabricInterface) -> Self {
+ Self::Openfabric(value)
+ }
+}
+
+impl From<ospf::OspfInterface> for Interface {
+ fn from(value: ospf::OspfInterface) -> Self {
+ Self::Ospf(value)
+ }
+}
+
+#[derive(Error, Debug)]
+pub enum FrrWordError {
+ #[error("word is empty")]
+ IsEmpty,
+ #[error("word contains invalid character")]
+ InvalidCharacter,
+}
+
+/// A simple FRR Word.
+///
+/// Every string argument or value in FRR is an FrrWord. FrrWords must only contain ascii
+/// characters and must not have a whitespace.
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct FrrWord(String);
+
+impl FrrWord {
+ pub fn new<T: AsRef<str> + Into<String>>(name: T) -> Result<Self, FrrWordError> {
+ if name.as_ref().is_empty() {
+ return Err(FrrWordError::IsEmpty);
+ }
+
+ if name
+ .as_ref()
+ .as_bytes()
+ .iter()
+ .any(|c| !c.is_ascii() || c.is_ascii_whitespace())
+ {
+ eprintln!("invalid char in: \"{}\"", name.as_ref());
+ return Err(FrrWordError::InvalidCharacter);
+ }
+
+ Ok(Self(name.into()))
+ }
+}
+
+impl FromStr for FrrWord {
+ type Err = FrrWordError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ Self::new(s)
+ }
+}
+
+impl Display for FrrWord {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl AsRef<str> for FrrWord {
+ fn as_ref(&self) -> &str {
+ &self.0
+ }
+}
+
+#[derive(Error, Debug)]
+pub enum CommonInterfaceNameError {
+ #[error("interface name too long")]
+ TooLong,
+}
+
+/// Name of a interface, which is common between all protocols.
+///
+/// FRR itself doesn't enforce any limits, but the kernel does. Linux only allows interface names
+/// to be a maximum of 16 bytes. This is enforced by this struct.
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct CommonInterfaceName(String);
+
+impl TryFrom<&str> for CommonInterfaceName {
+ type Error = CommonInterfaceNameError;
+
+ fn try_from(value: &str) -> Result<Self, Self::Error> {
+ Self::new(value)
+ }
+}
+
+impl TryFrom<String> for CommonInterfaceName {
+ type Error = CommonInterfaceNameError;
+
+ fn try_from(value: String) -> Result<Self, Self::Error> {
+ Self::new(value)
+ }
+}
+
+impl CommonInterfaceName {
+ pub fn new<T: AsRef<str> + Into<String>>(s: T) -> Result<Self, CommonInterfaceNameError> {
+ if s.as_ref().len() <= 15 {
+ Ok(Self(s.into()))
+ } else {
+ Err(CommonInterfaceNameError::TooLong)
+ }
+ }
+}
+
+impl Display for CommonInterfaceName {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+/// Main FRR config.
+///
+/// Contains the two main frr building blocks: routers and interfaces. It also holds other
+/// top-level FRR options, such as access-lists, router-maps and protocol-routemaps. This struct
+/// gets generated using the `FrrConfigBuilder` in `proxmox-ve-config`.
+#[derive(Clone, Debug, PartialEq, Eq, Default)]
+pub struct FrrConfig {
+ pub router: BTreeMap<RouterName, Router>,
+ pub interfaces: BTreeMap<InterfaceName, Interface>,
+ pub access_lists: Vec<AccessList>,
+ pub routemaps: Vec<RouteMap>,
+ pub protocol_routemaps: BTreeSet<ProtocolRouteMap>,
+}
+
+impl FrrConfig {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ pub fn router(&self) -> impl Iterator<Item = (&RouterName, &Router)> + '_ {
+ self.router.iter()
+ }
+
+ pub fn interfaces(&self) -> impl Iterator<Item = (&InterfaceName, &Interface)> + '_ {
+ self.interfaces.iter()
+ }
+
+ pub fn access_lists(&self) -> impl Iterator<Item = &AccessList> + '_ {
+ self.access_lists.iter()
+ }
+ pub fn routemaps(&self) -> impl Iterator<Item = &RouteMap> + '_ {
+ self.routemaps.iter()
+ }
+
+ pub fn protocol_routemaps(&self) -> impl Iterator<Item = &ProtocolRouteMap> + '_ {
+ self.protocol_routemaps.iter()
+ }
+}
diff --git a/proxmox-frr/src/openfabric.rs b/proxmox-frr/src/ser/openfabric.rs
similarity index 97%
rename from proxmox-frr/src/openfabric.rs
rename to proxmox-frr/src/ser/openfabric.rs
index 6e2a7200ab37..0f0c65062d36 100644
--- a/proxmox-frr/src/openfabric.rs
+++ b/proxmox-frr/src/ser/openfabric.rs
@@ -5,8 +5,8 @@ use proxmox_sdn_types::net::Net;
use thiserror::Error;
-use crate::FrrWord;
-use crate::FrrWordError;
+use crate::ser::FrrWord;
+use crate::ser::FrrWordError;
/// The name of a OpenFabric router. Is an FrrWord.
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
diff --git a/proxmox-frr/src/ospf.rs b/proxmox-frr/src/ser/ospf.rs
similarity index 99%
rename from proxmox-frr/src/ospf.rs
rename to proxmox-frr/src/ser/ospf.rs
index d0e098e099d2..67e39a45b8de 100644
--- a/proxmox-frr/src/ospf.rs
+++ b/proxmox-frr/src/ser/ospf.rs
@@ -4,7 +4,7 @@ use std::net::Ipv4Addr;
use thiserror::Error;
-use crate::{FrrWord, FrrWordError};
+use crate::ser::{FrrWord, FrrWordError};
/// The name of the ospf frr router.
///
diff --git a/proxmox-frr/src/route_map.rs b/proxmox-frr/src/ser/route_map.rs
similarity index 100%
rename from proxmox-frr/src/route_map.rs
rename to proxmox-frr/src/ser/route_map.rs
diff --git a/proxmox-frr/src/serializer.rs b/proxmox-frr/src/ser/serializer.rs
similarity index 99%
rename from proxmox-frr/src/serializer.rs
rename to proxmox-frr/src/ser/serializer.rs
index f8a3c7238d94..3a681e2f0d7a 100644
--- a/proxmox-frr/src/serializer.rs
+++ b/proxmox-frr/src/ser/serializer.rs
@@ -1,6 +1,6 @@
use std::fmt::{self, Write};
-use crate::{
+use crate::ser::{
openfabric::{OpenfabricInterface, OpenfabricRouter},
ospf::{OspfInterface, OspfRouter},
route_map::{AccessList, AccessListName, ProtocolRouteMap, RouteMap},
diff --git a/proxmox-ve-config/src/sdn/fabric/frr.rs b/proxmox-ve-config/src/sdn/fabric/frr.rs
index 486f7dc51dcb..10025b3544b9 100644
--- a/proxmox-ve-config/src/sdn/fabric/frr.rs
+++ b/proxmox-ve-config/src/sdn/fabric/frr.rs
@@ -1,12 +1,7 @@
use std::net::{IpAddr, Ipv4Addr};
use tracing;
-use proxmox_frr::ospf::{self, NetworkType};
-use proxmox_frr::route_map::{
- AccessAction, AccessList, AccessListName, AccessListRule, ProtocolRouteMap, ProtocolType,
- RouteMap, RouteMapMatch, RouteMapMatchInner, RouteMapName, RouteMapSet,
-};
-use proxmox_frr::{FrrConfig, FrrWord, Interface, InterfaceName, Router, RouterName};
+use proxmox_frr::ser::{self};
use proxmox_network_types::ip_address::Cidr;
use proxmox_sdn_types::net::Net;
@@ -26,7 +21,7 @@ use crate::sdn::fabric::{FabricConfig, FabricEntry};
pub fn build_fabric(
current_node: NodeId,
config: Valid<FabricConfig>,
- frr_config: &mut FrrConfig,
+ frr_config: &mut ser::FrrConfig,
) -> Result<(), anyhow::Error> {
let mut routemap_seq = 100;
let mut current_router_id: Option<Ipv4Addr> = None;
@@ -93,27 +88,31 @@ pub fn build_fabric(
}
if let Some(ipv4cidr) = fabric.ip_prefix() {
- let rule = AccessListRule {
- action: AccessAction::Permit,
+ let rule = ser::route_map::AccessListRule {
+ action: ser::route_map::AccessAction::Permit,
network: Cidr::from(ipv4cidr),
seq: None,
};
- let access_list_name =
- AccessListName::new(format!("pve_openfabric_{}_ips", fabric_id));
- frr_config.access_lists.push(AccessList {
+ let access_list_name = ser::route_map::AccessListName::new(format!(
+ "pve_openfabric_{}_ips",
+ fabric_id
+ ));
+ frr_config.access_lists.push(ser::route_map::AccessList {
name: access_list_name,
rules: vec![rule],
});
}
if let Some(ipv6cidr) = fabric.ip6_prefix() {
- let rule = AccessListRule {
- action: AccessAction::Permit,
+ let rule = ser::route_map::AccessListRule {
+ action: ser::route_map::AccessAction::Permit,
network: Cidr::from(ipv6cidr),
seq: None,
};
- let access_list_name =
- AccessListName::new(format!("pve_openfabric_{}_ip6s", fabric_id));
- frr_config.access_lists.push(AccessList {
+ let access_list_name = ser::route_map::AccessListName::new(format!(
+ "pve_openfabric_{}_ip6s",
+ fabric_id
+ ));
+ frr_config.access_lists.push(ser::route_map::AccessList {
name: access_list_name,
rules: vec![rule],
});
@@ -128,10 +127,12 @@ pub fn build_fabric(
));
routemap_seq += 10;
- let protocol_routemap = ProtocolRouteMap {
+ let protocol_routemap = ser::route_map::ProtocolRouteMap {
is_ipv6: false,
- protocol: ProtocolType::Openfabric,
- routemap_name: RouteMapName::new("pve_openfabric".to_owned()),
+ protocol: ser::route_map::ProtocolType::Openfabric,
+ routemap_name: ser::route_map::RouteMapName::new(
+ "pve_openfabric".to_owned(),
+ ),
};
frr_config.protocol_routemaps.insert(protocol_routemap);
@@ -145,10 +146,12 @@ pub fn build_fabric(
));
routemap_seq += 10;
- let protocol_routemap = ProtocolRouteMap {
+ let protocol_routemap = ser::route_map::ProtocolRouteMap {
is_ipv6: true,
- protocol: ProtocolType::Openfabric,
- routemap_name: RouteMapName::new("pve_openfabric6".to_owned()),
+ protocol: ser::route_map::ProtocolType::Openfabric,
+ routemap_name: ser::route_map::RouteMapName::new(
+ "pve_openfabric6".to_owned(),
+ ),
};
frr_config.protocol_routemaps.insert(protocol_routemap);
@@ -164,8 +167,8 @@ pub fn build_fabric(
let fabric = ospf_entry.fabric_section();
- let frr_word_area = FrrWord::new(fabric.properties().area.to_string())?;
- let frr_area = ospf::Area::new(frr_word_area)?;
+ let frr_word_area = ser::FrrWord::new(fabric.properties().area.to_string())?;
+ let frr_area = ser::ospf::Area::new(frr_word_area)?;
let (router_name, router_item) = build_ospf_router(*router_id)?;
frr_config.router.insert(router_name, router_item);
@@ -196,17 +199,18 @@ pub fn build_fabric(
}
}
- let access_list_name = AccessListName::new(format!("pve_ospf_{}_ips", fabric_id));
+ let access_list_name =
+ ser::route_map::AccessListName::new(format!("pve_ospf_{}_ips", fabric_id));
- let rule = AccessListRule {
- action: AccessAction::Permit,
+ let rule = ser::route_map::AccessListRule {
+ action: ser::route_map::AccessAction::Permit,
network: Cidr::from(
fabric.ip_prefix().expect("fabric must have a ipv4 prefix"),
),
seq: None,
};
- frr_config.access_lists.push(AccessList {
+ frr_config.access_lists.push(ser::route_map::AccessList {
name: access_list_name,
rules: vec![rule],
});
@@ -220,10 +224,10 @@ pub fn build_fabric(
routemap_seq += 10;
frr_config.routemaps.push(routemap);
- let protocol_routemap = ProtocolRouteMap {
+ let protocol_routemap = ser::route_map::ProtocolRouteMap {
is_ipv6: false,
- protocol: ProtocolType::Ospf,
- routemap_name: RouteMapName::new("pve_ospf".to_owned()),
+ protocol: ser::route_map::ProtocolType::Ospf,
+ routemap_name: ser::route_map::RouteMapName::new("pve_ospf".to_owned()),
};
frr_config.protocol_routemaps.insert(protocol_routemap);
@@ -234,10 +238,10 @@ pub fn build_fabric(
}
/// Helper that builds a OSPF router with a the router_id.
-fn build_ospf_router(router_id: Ipv4Addr) -> Result<(RouterName, Router), anyhow::Error> {
- let ospf_router = proxmox_frr::ospf::OspfRouter { router_id };
- let router_item = Router::Ospf(ospf_router);
- let router_name = RouterName::Ospf(proxmox_frr::ospf::OspfRouterName);
+fn build_ospf_router(router_id: Ipv4Addr) -> Result<(ser::RouterName, ser::Router), anyhow::Error> {
+ let ospf_router = ser::ospf::OspfRouter { router_id };
+ let router_item = ser::Router::Ospf(ospf_router);
+ let router_name = ser::RouterName::Ospf(ser::ospf::OspfRouterName);
Ok((router_name, router_item))
}
@@ -245,45 +249,45 @@ fn build_ospf_router(router_id: Ipv4Addr) -> Result<(RouterName, Router), anyhow
fn build_openfabric_router(
fabric_id: &FabricId,
net: Net,
-) -> Result<(RouterName, Router), anyhow::Error> {
- let ofr = proxmox_frr::openfabric::OpenfabricRouter { net };
- let router_item = Router::Openfabric(ofr);
- let frr_word_id = FrrWord::new(fabric_id.to_string())?;
- let router_name = RouterName::Openfabric(frr_word_id.into());
+) -> Result<(ser::RouterName, ser::Router), anyhow::Error> {
+ let ofr = ser::openfabric::OpenfabricRouter { net };
+ let router_item = ser::Router::Openfabric(ofr);
+ let frr_word_id = ser::FrrWord::new(fabric_id.to_string())?;
+ let router_name = ser::RouterName::Openfabric(frr_word_id.into());
Ok((router_name, router_item))
}
/// Helper that builds a OSPF interface from an [`ospf::Area`] and the [`OspfInterfaceProperties`].
fn build_ospf_interface(
- area: ospf::Area,
+ area: ser::ospf::Area,
interface: &OspfInterfaceProperties,
-) -> Result<(Interface, InterfaceName), anyhow::Error> {
- let frr_interface = proxmox_frr::ospf::OspfInterface {
+) -> Result<(ser::Interface, ser::InterfaceName), anyhow::Error> {
+ let frr_interface = ser::ospf::OspfInterface {
area,
// Interfaces are always none-passive
passive: None,
network_type: if interface.ip.is_some() {
None
} else {
- Some(NetworkType::PointToPoint)
+ Some(ser::ospf::NetworkType::PointToPoint)
},
};
- let interface_name = InterfaceName::Ospf(interface.name.as_str().try_into()?);
+ let interface_name = ser::InterfaceName::Ospf(interface.name.as_str().try_into()?);
Ok((frr_interface.into(), interface_name))
}
/// Helper that builds the OSPF dummy interface using the [`FabricId`] and the [`ospf::Area`].
fn build_ospf_dummy_interface(
fabric_id: &FabricId,
- area: ospf::Area,
-) -> Result<(Interface, InterfaceName), anyhow::Error> {
- let frr_interface = proxmox_frr::ospf::OspfInterface {
+ area: ser::ospf::Area,
+) -> Result<(ser::Interface, ser::InterfaceName), anyhow::Error> {
+ let frr_interface = ser::ospf::OspfInterface {
area,
passive: Some(true),
network_type: None,
};
- let interface_name = InterfaceName::Openfabric(format!("dummy_{}", fabric_id).try_into()?);
+ let interface_name = ser::InterfaceName::Openfabric(format!("dummy_{}", fabric_id).try_into()?);
Ok((frr_interface.into(), interface_name))
}
@@ -297,9 +301,9 @@ fn build_openfabric_interface(
fabric_config: &OpenfabricProperties,
is_ipv4: bool,
is_ipv6: bool,
-) -> Result<(Interface, InterfaceName), anyhow::Error> {
- let frr_word = FrrWord::new(fabric_id.to_string())?;
- let mut frr_interface = proxmox_frr::openfabric::OpenfabricInterface {
+) -> Result<(ser::Interface, ser::InterfaceName), anyhow::Error> {
+ let frr_word = ser::FrrWord::new(fabric_id.to_string())?;
+ let mut frr_interface = ser::openfabric::OpenfabricInterface {
fabric_id: frr_word.into(),
// Every interface is not passive by default
passive: None,
@@ -315,7 +319,7 @@ fn build_openfabric_interface(
if frr_interface.hello_interval.is_none() {
frr_interface.hello_interval = fabric_config.hello_interval;
}
- let interface_name = InterfaceName::Openfabric(interface.name.as_str().try_into()?);
+ let interface_name = ser::InterfaceName::Openfabric(interface.name.as_str().try_into()?);
Ok((frr_interface.into(), interface_name))
}
@@ -324,9 +328,9 @@ fn build_openfabric_dummy_interface(
fabric_id: &FabricId,
is_ipv4: bool,
is_ipv6: bool,
-) -> Result<(Interface, InterfaceName), anyhow::Error> {
- let frr_word = FrrWord::new(fabric_id.to_string())?;
- let frr_interface = proxmox_frr::openfabric::OpenfabricInterface {
+) -> Result<(ser::Interface, ser::InterfaceName), anyhow::Error> {
+ let frr_word = ser::FrrWord::new(fabric_id.to_string())?;
+ let frr_interface = ser::openfabric::OpenfabricInterface {
fabric_id: frr_word.into(),
hello_interval: None,
passive: Some(true),
@@ -335,29 +339,37 @@ fn build_openfabric_dummy_interface(
is_ipv4,
is_ipv6,
};
- let interface_name = InterfaceName::Openfabric(format!("dummy_{}", fabric_id).try_into()?);
+ let interface_name = ser::InterfaceName::Openfabric(format!("dummy_{}", fabric_id).try_into()?);
Ok((frr_interface.into(), interface_name))
}
/// Helper that builds a RouteMap for the OpenFabric protocol.
-fn build_openfabric_routemap(fabric_id: &FabricId, router_ip: IpAddr, seq: u32) -> RouteMap {
+fn build_openfabric_routemap(
+ fabric_id: &FabricId,
+ router_ip: IpAddr,
+ seq: u32,
+) -> ser::route_map::RouteMap {
let routemap_name = match router_ip {
- IpAddr::V4(_) => RouteMapName::new("pve_openfabric".to_owned()),
- IpAddr::V6(_) => RouteMapName::new("pve_openfabric6".to_owned()),
+ IpAddr::V4(_) => ser::route_map::RouteMapName::new("pve_openfabric".to_owned()),
+ IpAddr::V6(_) => ser::route_map::RouteMapName::new("pve_openfabric6".to_owned()),
};
- RouteMap {
+ ser::route_map::RouteMap {
name: routemap_name.clone(),
seq,
- action: AccessAction::Permit,
+ action: ser::route_map::AccessAction::Permit,
matches: vec![match router_ip {
- IpAddr::V4(_) => RouteMapMatch::V4(RouteMapMatchInner::IpAddress(AccessListName::new(
- format!("pve_openfabric_{fabric_id}_ips"),
- ))),
- IpAddr::V6(_) => RouteMapMatch::V6(RouteMapMatchInner::IpAddress(AccessListName::new(
- format!("pve_openfabric_{fabric_id}_ip6s"),
- ))),
+ IpAddr::V4(_) => {
+ ser::route_map::RouteMapMatch::V4(ser::route_map::RouteMapMatchInner::IpAddress(
+ ser::route_map::AccessListName::new(format!("pve_openfabric_{fabric_id}_ips")),
+ ))
+ }
+ IpAddr::V6(_) => {
+ ser::route_map::RouteMapMatch::V6(ser::route_map::RouteMapMatchInner::IpAddress(
+ ser::route_map::AccessListName::new(format!("pve_openfabric_{fabric_id}_ip6s")),
+ ))
+ }
}],
- sets: vec![RouteMapSet::IpSrc(router_ip)],
+ sets: vec![ser::route_map::RouteMapSet::IpSrc(router_ip)],
}
}
@@ -366,17 +378,19 @@ fn build_ospf_dummy_routemap(
fabric_id: &FabricId,
router_ip: Ipv4Addr,
seq: u32,
-) -> Result<RouteMap, anyhow::Error> {
- let routemap_name = RouteMapName::new("pve_ospf".to_owned());
+) -> Result<ser::route_map::RouteMap, anyhow::Error> {
+ let routemap_name = ser::route_map::RouteMapName::new("pve_ospf".to_owned());
// create route-map
- let routemap = RouteMap {
+ let routemap = ser::route_map::RouteMap {
name: routemap_name.clone(),
seq,
- action: AccessAction::Permit,
- matches: vec![RouteMapMatch::V4(RouteMapMatchInner::IpAddress(
- AccessListName::new(format!("pve_ospf_{fabric_id}_ips")),
- ))],
- sets: vec![RouteMapSet::IpSrc(IpAddr::from(router_ip))],
+ action: ser::route_map::AccessAction::Permit,
+ matches: vec![ser::route_map::RouteMapMatch::V4(
+ ser::route_map::RouteMapMatchInner::IpAddress(ser::route_map::AccessListName::new(
+ format!("pve_ospf_{fabric_id}_ips"),
+ )),
+ )],
+ sets: vec![ser::route_map::RouteMapSet::IpSrc(IpAddr::from(router_ip))],
};
Ok(routemap)
diff --git a/proxmox-ve-config/src/sdn/frr.rs b/proxmox-ve-config/src/sdn/frr.rs
index f7929c1f6c16..5d4e4b2ebdbd 100644
--- a/proxmox-ve-config/src/sdn/frr.rs
+++ b/proxmox-ve-config/src/sdn/frr.rs
@@ -1,6 +1,6 @@
use std::collections::{BTreeMap, BTreeSet};
-use proxmox_frr::FrrConfig;
+use proxmox_frr::ser::FrrConfig;
use crate::common::valid::Valid;
use crate::sdn::fabric::{section_config::node::NodeId, FabricConfig};
diff --git a/proxmox-ve-config/tests/fabric/main.rs b/proxmox-ve-config/tests/fabric/main.rs
index 47bbbeb77886..09629d406449 100644
--- a/proxmox-ve-config/tests/fabric/main.rs
+++ b/proxmox-ve-config/tests/fabric/main.rs
@@ -1,5 +1,5 @@
#![cfg(feature = "frr")]
-use proxmox_frr::serializer::dump;
+use proxmox_frr::ser::serializer::dump;
use proxmox_ve_config::sdn::{
fabric::{section_config::node::NodeId, FabricConfig},
frr::FrrConfigBuilder,
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
* [pve-devel] [PATCH proxmox-ve-rs v2 2/2] frr: add deserialization types for openfabric and ospf
2025-08-22 9:00 [pve-devel] [PATCH manager/network/proxmox{-ve-rs, -perl-rs} v2 00/12] Add fabric status view Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-ve-rs v2 1/2] frr: make room for deserialization structs Gabriel Goller
@ 2025-08-22 9:00 ` Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-perl-rs v2 1/4] pve: fabrics: update proxmox-frr import path Gabriel Goller
` (10 subsequent siblings)
12 siblings, 0 replies; 25+ messages in thread
From: Gabriel Goller @ 2025-08-22 9:00 UTC (permalink / raw)
To: pve-devel
These are used to deserialize `vtysh` command outputs. The output is in
json if the `json` parameter is appended to the command. Currently the
following commands are parsed:
* show openfabric neighbor
* show ip ospf neighbor
* show ip route <protocol>
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
proxmox-frr/Cargo.toml | 1 +
proxmox-frr/debian/control | 4 ++
proxmox-frr/src/de/mod.rs | 104 +++++++++++++++++++++++++++++++
proxmox-frr/src/de/openfabric.rs | 42 +++++++++++++
proxmox-frr/src/de/ospf.rs | 57 +++++++++++++++++
proxmox-frr/src/lib.rs | 1 +
6 files changed, 209 insertions(+)
create mode 100644 proxmox-frr/src/de/mod.rs
create mode 100644 proxmox-frr/src/de/openfabric.rs
create mode 100644 proxmox-frr/src/de/ospf.rs
diff --git a/proxmox-frr/Cargo.toml b/proxmox-frr/Cargo.toml
index 47fb8bb3969c..d1a24a899b55 100644
--- a/proxmox-frr/Cargo.toml
+++ b/proxmox-frr/Cargo.toml
@@ -13,6 +13,7 @@ rust-version.workspace = true
thiserror = { workspace = true }
anyhow = "1"
tracing = "0.1"
+serde = { workspace = true, features = [ "derive" ] }
proxmox-network-types = { workspace = true }
proxmox-sdn-types = { workspace = true }
diff --git a/proxmox-frr/debian/control b/proxmox-frr/debian/control
index 544fc3ec9ec4..aa74860f2b2f 100644
--- a/proxmox-frr/debian/control
+++ b/proxmox-frr/debian/control
@@ -9,6 +9,8 @@ Build-Depends-Arch: cargo:native <!nocheck>,
librust-anyhow-1+default-dev <!nocheck>,
librust-proxmox-network-types-0.1+default-dev (>= 0.1.1-~~) <!nocheck>,
librust-proxmox-sdn-types-0.1+default-dev <!nocheck>,
+ librust-serde-1+default-dev <!nocheck>,
+ librust-serde-1+derive-dev <!nocheck>,
librust-thiserror-2+default-dev <!nocheck>,
librust-tracing-0.1+default-dev <!nocheck>
Maintainer: Proxmox Support Team <support@proxmox.com>
@@ -27,6 +29,8 @@ Depends:
librust-anyhow-1+default-dev,
librust-proxmox-network-types-0.1+default-dev (>= 0.1.1-~~),
librust-proxmox-sdn-types-0.1+default-dev,
+ librust-serde-1+default-dev,
+ librust-serde-1+derive-dev,
librust-thiserror-2+default-dev,
librust-tracing-0.1+default-dev
Provides:
diff --git a/proxmox-frr/src/de/mod.rs b/proxmox-frr/src/de/mod.rs
new file mode 100644
index 000000000000..43890506253d
--- /dev/null
+++ b/proxmox-frr/src/de/mod.rs
@@ -0,0 +1,104 @@
+use std::{collections::HashMap, net::IpAddr};
+
+use proxmox_network_types::ip_address::Cidr;
+use serde::{Deserialize, Serialize};
+
+pub mod openfabric;
+pub mod ospf;
+
+/// A nexthop of a route
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct NextHop {
+ /// Flags
+ pub flags: i32,
+ /// If the route is in the FIB (Forward Information Base)
+ pub fib: Option<bool>,
+ /// IP of the nexthop
+ pub ip: Option<IpAddr>,
+ /// AFI (either IPv4, IPv6 or something else)
+ pub afi: String,
+ /// Index of the outgoing interface
+ #[serde(rename = "interfaceIndex")]
+ pub interface_index: i32,
+ #[serde(rename = "interfaceName")]
+ /// Name of the outgoing interface
+ pub interface_name: String,
+ /// If the nexthop is active
+ pub active: bool,
+ /// If the route has the onlink flag. Onlink means that we pretend that the nexthop is
+ /// directly attached to this link, even if it does not match any interface prefix.
+ #[serde(rename = "onLink")]
+ pub on_link: bool,
+ /// Remap-Source, this rewrites the source address to the following address, if this
+ /// nexthop is used.
+ #[serde(rename = "rmapSource")]
+ pub remap_source: Option<IpAddr>,
+ /// Weight of the nexthop
+ pub weight: i32,
+}
+
+/// route
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct Route {
+ /// Prefix of the route
+ pub prefix: Cidr,
+ /// Prefix Length
+ #[serde(rename = "prefixLen")]
+ pub prefix_len: u32,
+ /// Protocol from which the route originates
+ pub protocol: String,
+ /// VRF id
+ #[serde(rename = "vrfId")]
+ pub vrf_id: u32,
+ /// VRF name
+ #[serde(rename = "vrfName")]
+ pub vrf_name: String,
+ /// If the route has been selected (if multiple of the same routes from different
+ /// daemons exist, the one with the shortest distance is selected).
+ pub selected: Option<bool>,
+ /// Destination Selected
+ #[serde(rename = "destSelected")]
+ pub destination_selected: Option<bool>,
+ /// Distance of the route
+ pub distance: Option<i32>,
+ /// Metric of the route
+ pub metric: i32,
+ /// If the route is installed in the kernel routing table
+ pub installed: Option<bool>,
+ /// The id of the routing table
+ pub table: i32,
+ /// Internal Status
+ #[serde(rename = "internalStatus")]
+ pub internal_status: i32,
+ /// Internal Flags
+ #[serde(rename = "internalFlags")]
+ pub internal_flags: i32,
+ /// Internal Nexthop Num, this is the id to lookup the nexthop (visible in e.g. `ip
+ /// nexthop ls`).
+ #[serde(rename = "internalNextHopNum")]
+ pub internal_nexthop_num: i32,
+ /// Internal Nexthop Active Num
+ #[serde(rename = "internalNextHopActiveNum")]
+ pub internal_nexthop_active_num: i32,
+ /// Nexthop Group Id
+ #[serde(rename = "nexthopGroupId")]
+ pub nexthop_group_id: i32,
+ /// Installed Nexthop Group Id
+ #[serde(rename = "installedNexthopGroupId")]
+ pub installed_nexthop_group_id: Option<i32>,
+ /// The uptime of the route
+ pub uptime: String,
+
+ /// Array of all the nexthops associated with this route. When you have e.g. two
+ /// connections between two nodes, there is going to be one route, but two nexthops.
+ pub nexthops: Vec<NextHop>,
+}
+
+/// Struct to parse zebra routes by FRR.
+///
+/// To get the routes from FRR, instead of asking the daemon of every protocol for their
+/// routes we simply ask zebra which routes have been inserted and filter them by protocol.
+/// The following command is used to accomplish this: `show ip route <protocol> json`.
+/// This struct can be used the deserialize the output of that command.
+#[derive(Debug, Serialize, Deserialize, Default)]
+pub struct Routes(pub HashMap<Cidr, Vec<Route>>);
diff --git a/proxmox-frr/src/de/openfabric.rs b/proxmox-frr/src/de/openfabric.rs
new file mode 100644
index 000000000000..99d281f24bcd
--- /dev/null
+++ b/proxmox-frr/src/de/openfabric.rs
@@ -0,0 +1,42 @@
+use serde::{Deserialize, Serialize};
+
+/// Adjacency information
+///
+/// Circuits are Layer-2 Broadcast domains (Either point-to-point or LAN).
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Circuit {
+ /// The circuit id
+ pub circuit: u32,
+ /// The hostname of the adjacency peer
+ pub adj: Option<String>,
+ /// The interface on which this adjacency exists
+ pub interface: Option<String>,
+ /// If the adjacent router is a L1 or L2 router
+ pub level: Option<u32>,
+ /// The state of the adjacency, this is "Up" when everything is well
+ pub state: Option<String>,
+ /// When the adjacency expires
+ #[serde(rename = "expires-in")]
+ pub expires_in: Option<String>,
+ /// Subnetwork Point of Attachment
+ pub snpa: Option<String>,
+}
+
+/// An openfabric area the same as SDN fabric.
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Area {
+ /// The are name, this is the same as the fabric_id, so the name of the fabric.
+ pub area: String,
+ /// Circuits are Layer-2 Broadcast domains (Either point-to-point or LAN).
+ pub circuits: Vec<Circuit>,
+}
+
+/// The parsed neighbors.
+///
+/// This models the output of:
+/// `vtysh -c 'show openfabric neighbor json'`.
+#[derive(Debug, Serialize, Deserialize, Default)]
+pub struct Neighbors {
+ /// Every sdn fabric is also an openfabric 'area'
+ pub areas: Vec<Area>,
+}
diff --git a/proxmox-frr/src/de/ospf.rs b/proxmox-frr/src/de/ospf.rs
new file mode 100644
index 000000000000..0e813ff1e614
--- /dev/null
+++ b/proxmox-frr/src/de/ospf.rs
@@ -0,0 +1,57 @@
+use std::collections::HashMap;
+
+use serde::{Deserialize, Serialize};
+
+/// Information about the Neighbor (Peer) of the Adjacency.
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Neighbor {
+ /// The full state of the neighbor. This is "{converged}/{role}".
+ #[serde(rename = "nbrState")]
+ pub neighbor_state: String,
+ /// Priority of the Neighbor
+ #[serde(rename = "nbrPriority")]
+ pub neighbor_priority: u32,
+ /// The current state of the adjancecy, this is a complex state machine with many
+ /// states. The most important ones are "Full" if the full table has been exchanged
+ /// and "Init" when the adjacency has been formed but no routing information has
+ /// been exchanged.
+ pub converged: String,
+ /// Role of the peer (If he's a designated router (DR) or not (DROther)
+ pub role: String,
+ /// Uptime in milliseconds
+ #[serde(rename = "upTimeInMsec")]
+ pub up_time_in_msec: u64,
+ /// Router Dead Interval Timer Due in milliseconds
+ #[serde(rename = "routerDeadIntervalTimerDueMsec")]
+ pub router_dead_interval_timer_due_msec: u64,
+ /// Uptime of the adjacency
+ #[serde(rename = "upTime")]
+ pub up_time: String,
+ /// Expires in countdown
+ #[serde(rename = "deadTime")]
+ pub dead_time: String,
+ /// The remote interface address, so the address of the other peer.
+ #[serde(rename = "ifaceAddress")]
+ pub interface_address: String,
+ /// The interface name of this adjacency. This is always a combination of interface
+ /// name and address. e.g. "ens21:5.5.5.3".
+ #[serde(rename = "ifaceName")]
+ pub interface_name: String,
+ /// Link State Retransmission List Counter
+ #[serde(rename = "linkStateRetransmissionListCounter")]
+ pub link_state_retransmission_list_counter: u32,
+ /// Link State Request List Counter
+ #[serde(rename = "linkStateRequestListCounter")]
+ pub link_state_request_list_counter: u32,
+ /// Database Summary List Counter
+ #[serde(rename = "databaseSummaryListCounter")]
+ pub database_summary_list_counter: u32,
+}
+
+/// The parsed OSPF neighbors
+#[derive(Debug, Serialize, Deserialize, Default)]
+pub struct Neighbors {
+ /// The OSPF neighbors. This is nearly always a ip-address - neighbor mapping.
+ pub neighbors: HashMap<String, Vec<Neighbor>>,
+}
diff --git a/proxmox-frr/src/lib.rs b/proxmox-frr/src/lib.rs
index 35b62cb39c91..2e6ab62c6119 100644
--- a/proxmox-frr/src/lib.rs
+++ b/proxmox-frr/src/lib.rs
@@ -1 +1,2 @@
+pub mod de;
pub mod ser;
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
* [pve-devel] [PATCH proxmox-perl-rs v2 1/4] pve: fabrics: update proxmox-frr import path
2025-08-22 9:00 [pve-devel] [PATCH manager/network/proxmox{-ve-rs, -perl-rs} v2 00/12] Add fabric status view Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-ve-rs v2 1/2] frr: make room for deserialization structs Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-ve-rs v2 2/2] frr: add deserialization types for openfabric and ospf Gabriel Goller
@ 2025-08-22 9:00 ` Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-perl-rs v2 2/4] fabrics: add function to get status of fabric Gabriel Goller
` (9 subsequent siblings)
12 siblings, 0 replies; 25+ messages in thread
From: Gabriel Goller @ 2025-08-22 9:00 UTC (permalink / raw)
To: pve-devel
Update the proxmox-frr import path to the new `ser` and `de` subfolders.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
pve-rs/src/bindings/sdn/fabrics.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pve-rs/src/bindings/sdn/fabrics.rs b/pve-rs/src/bindings/sdn/fabrics.rs
index 587b1d68c8fb..1dc8bf4320e6 100644
--- a/pve-rs/src/bindings/sdn/fabrics.rs
+++ b/pve-rs/src/bindings/sdn/fabrics.rs
@@ -16,7 +16,7 @@ pub mod pve_rs_sdn_fabrics {
use serde::{Deserialize, Serialize};
use perlmod::Value;
- use proxmox_frr::serializer::to_raw_config;
+ use proxmox_frr::ser::serializer::to_raw_config;
use proxmox_network_types::ip_address::{Cidr, Ipv4Cidr, Ipv6Cidr};
use proxmox_section_config::typed::SectionConfigData;
use proxmox_ve_config::common::valid::Validatable;
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
* [pve-devel] [PATCH proxmox-perl-rs v2 2/4] fabrics: add function to get status of fabric
2025-08-22 9:00 [pve-devel] [PATCH manager/network/proxmox{-ve-rs, -perl-rs} v2 00/12] Add fabric status view Gabriel Goller
` (2 preceding siblings ...)
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-perl-rs v2 1/4] pve: fabrics: update proxmox-frr import path Gabriel Goller
@ 2025-08-22 9:00 ` Gabriel Goller
2025-08-25 8:11 ` Wolfgang Bumiller
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-perl-rs v2 3/4] fabrics: add function to get all routes distributed by the fabrics Gabriel Goller
` (8 subsequent siblings)
12 siblings, 1 reply; 25+ messages in thread
From: Gabriel Goller @ 2025-08-22 9:00 UTC (permalink / raw)
To: pve-devel
Add a function to get the status of a fabric. This is the status which
will then be inserted into the pvestatd daemon and returned through the
resources api. In order the generate the HashMap of statuses for all
fabrics we need to read the fabric config and execute a vtysh (frr)
command to get the routes of the corresponding fabric. If there is at
least one route which is related to the fabric, the fabric is considered
"ok".
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
pve-rs/src/bindings/sdn/fabrics.rs | 194 +++++++++++++++++++++++++++++
1 file changed, 194 insertions(+)
diff --git a/pve-rs/src/bindings/sdn/fabrics.rs b/pve-rs/src/bindings/sdn/fabrics.rs
index 1dc8bf4320e6..3f70d421e582 100644
--- a/pve-rs/src/bindings/sdn/fabrics.rs
+++ b/pve-rs/src/bindings/sdn/fabrics.rs
@@ -9,8 +9,10 @@ pub mod pve_rs_sdn_fabrics {
use std::fmt::Write;
use std::net::IpAddr;
use std::ops::Deref;
+ use std::process::Command;
use std::sync::Mutex;
+ use anyhow::Context;
use anyhow::Error;
use openssl::hash::{MessageDigest, hash};
use serde::{Deserialize, Serialize};
@@ -578,4 +580,196 @@ pub mod pve_rs_sdn_fabrics {
Ok(interfaces)
}
+
+ /// This module contains status-related structs that represent Routes and Neighbors for all
+ /// protocols
+ pub mod status {
+ use std::collections::{HashMap, HashSet};
+
+ use serde::Serialize;
+
+ use proxmox_frr::de::{self};
+ use proxmox_ve_config::sdn::fabric::{
+ FabricConfig,
+ section_config::{fabric::FabricId, node::Node as ConfigNode},
+ };
+
+ /// Protocol
+ #[derive(Debug, Serialize, Clone, Copy)]
+ pub enum Protocol {
+ /// Openfabric
+ Openfabric,
+ /// OSPF
+ Ospf,
+ }
+
+ /// The status of a fabric.
+ #[derive(Debug, Serialize)]
+ pub enum FabricStatus {
+ /// The fabric exists and has a route
+ #[serde(rename = "ok")]
+ Ok,
+ /// The fabric does not exist or doesn't distribute any routes
+ #[serde(rename = "not ok")]
+ NotOk,
+ }
+
+ /// Status of a fabric.
+ ///
+ /// Check if there are any routes, if yes, then the status is ok, otherwise not ok.
+ #[derive(Debug, Serialize)]
+ pub struct Status {
+ #[serde(rename = "type")]
+ ty: String,
+ status: FabricStatus,
+ protocol: Protocol,
+ sdn: FabricId,
+ sdn_type: String,
+ }
+
+ /// Parsed routes for all protocols
+ ///
+ /// These are the routes parsed from the json output of:
+ /// `vtysh -c 'show ip route <protocol> json'`.
+ #[derive(Debug, Serialize)]
+ pub struct RoutesParsed {
+ /// All openfabric routes in FRR
+ pub openfabric: de::Routes,
+ /// All ospf routes in FRR
+ pub ospf: de::Routes,
+ }
+
+ impl TryInto<HashMap<FabricId, Status>> for RoutesParsed {
+ type Error = anyhow::Error;
+
+ fn try_into(self) -> Result<HashMap<FabricId, Status>, 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")?;
+ let config = FabricConfig::parse_section_config(&raw_config)?;
+
+ let mut stats: HashMap<FabricId, Status> = HashMap::new();
+
+ for (nodeid, node) in config.values().flat_map(|entry| {
+ entry
+ .nodes()
+ .map(|(id, node)| (id.to_string(), node.clone()))
+ }) {
+ if nodeid != hostname {
+ continue;
+ }
+ let fabric_id = node.id().fabric_id().clone();
+
+ let current_protocol = match &node {
+ ConfigNode::Openfabric(_) => Protocol::Openfabric,
+ ConfigNode::Ospf(_) => Protocol::Ospf,
+ };
+
+ let mut all_routes = HashMap::new();
+ match &node {
+ ConfigNode::Openfabric(_) => all_routes.extend(&self.openfabric.0),
+ ConfigNode::Ospf(_) => all_routes.extend(&self.ospf.0),
+ }
+
+ // get interfaces
+ let interface_names: HashSet<String> = match node {
+ 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(),
+ };
+
+ // determine status by checking if any routes exist for our interfaces
+ let has_routes = all_routes.iter().any(|(_, v)| {
+ v.iter().any(|route| {
+ route
+ .nexthops
+ .iter()
+ .any(|nexthop| interface_names.contains(&nexthop.interface_name))
+ })
+ });
+
+ let fabric = Status {
+ ty: "sdn".to_owned(),
+ status: if has_routes {
+ FabricStatus::Ok
+ } else {
+ FabricStatus::NotOk
+ },
+ sdn_type: "fabric".to_string(),
+ protocol: current_protocol,
+ sdn: fabric_id.clone(),
+ };
+ stats.insert(fabric_id, fabric);
+ }
+
+ Ok(stats)
+ }
+ }
+ }
+
+ /// 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.
+ /// Check if there are any routes in the routing table that use the interface specified in the
+ /// config. If there are, show "ok" as status, otherwise "not ok".
+ #[export]
+ fn status() -> Result<HashMap<FabricId, status::Status>, 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()
+ }
}
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
* [pve-devel] [PATCH proxmox-perl-rs v2 3/4] fabrics: add function to get all routes distributed by the fabrics
2025-08-22 9:00 [pve-devel] [PATCH manager/network/proxmox{-ve-rs, -perl-rs} v2 00/12] Add fabric status view Gabriel Goller
` (3 preceding siblings ...)
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-perl-rs v2 2/4] fabrics: add function to get status of fabric Gabriel Goller
@ 2025-08-22 9:00 ` Gabriel Goller
2025-08-25 8:22 ` Wolfgang Bumiller
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-perl-rs v2 4/4] fabrics: add function to get all neighbors of the fabric Gabriel Goller
` (7 subsequent siblings)
12 siblings, 1 reply; 25+ messages in thread
From: Gabriel Goller @ 2025-08-22 9:00 UTC (permalink / raw)
To: pve-devel
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@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")?;
+ 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()))
+ }) {
+ if nodeid != hostname {
+ continue;
+ }
+ let fabric_id = node.id().fabric_id().clone();
+
+ let current_protocol = match &node {
+ ConfigNode::Openfabric(_) => Protocol::Openfabric,
+ ConfigNode::Ospf(_) => Protocol::Ospf,
+ };
+
+ // get interfaces
+ let interface_names: HashSet<String> = match node {
+ 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();
+ match current_protocol {
+ Protocol::Openfabric => all_routes.extend(&self.openfabric.0),
+ Protocol::Ospf => all_routes.extend(&self.ospf.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
^ permalink raw reply [flat|nested] 25+ messages in thread
* [pve-devel] [PATCH proxmox-perl-rs v2 4/4] fabrics: add function to get all neighbors of the fabric
2025-08-22 9:00 [pve-devel] [PATCH manager/network/proxmox{-ve-rs, -perl-rs} v2 00/12] Add fabric status view Gabriel Goller
` (4 preceding siblings ...)
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-perl-rs v2 3/4] fabrics: add function to get all routes distributed by the fabrics Gabriel Goller
@ 2025-08-22 9:00 ` Gabriel Goller
2025-08-25 8:28 ` Wolfgang Bumiller
2025-08-22 9:00 ` [pve-devel] [PATCH pve-network v2 1/3] fabrics: add fabrics status to SDN::status function Gabriel Goller
` (6 subsequent siblings)
12 siblings, 1 reply; 25+ messages in thread
From: Gabriel Goller @ 2025-08-22 9:00 UTC (permalink / raw)
To: pve-devel
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.goller@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")?;
+ 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()))
+ }) {
+ if nodeid != hostname {
+ continue;
+ }
+ let fabric_id = node.id().fabric_id().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
^ permalink raw reply [flat|nested] 25+ messages in thread
* [pve-devel] [PATCH pve-network v2 1/3] fabrics: add fabrics status to SDN::status function
2025-08-22 9:00 [pve-devel] [PATCH manager/network/proxmox{-ve-rs, -perl-rs} v2 00/12] Add fabric status view Gabriel Goller
` (5 preceding siblings ...)
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-perl-rs v2 4/4] fabrics: add function to get all neighbors of the fabric Gabriel Goller
@ 2025-08-22 9:00 ` Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH pve-network v2 2/3] fabrics: add api endpoint to return fabrics routes Gabriel Goller
` (5 subsequent siblings)
12 siblings, 0 replies; 25+ messages in thread
From: Gabriel Goller @ 2025-08-22 9:00 UTC (permalink / raw)
To: pve-devel
Add the fabrics to the global SDN::status function. This is needed in
pve-manager to insert the status into the pvestatd resources.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
src/PVE/API2/Network/SDN/Zones/Content.pm | 2 +-
src/PVE/API2/Network/SDN/Zones/Status.pm | 2 +-
src/PVE/Network/SDN.pm | 6 ++++--
src/test/debug/statuscheck.pl | 3 ++-
4 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/src/PVE/API2/Network/SDN/Zones/Content.pm b/src/PVE/API2/Network/SDN/Zones/Content.pm
index 76663210067d..2427abe70c93 100644
--- a/src/PVE/API2/Network/SDN/Zones/Content.pm
+++ b/src/PVE/API2/Network/SDN/Zones/Content.pm
@@ -70,7 +70,7 @@ __PACKAGE__->register_method({
my $res = [];
- my ($zone_status, $vnet_status) = PVE::Network::SDN::status();
+ my ($zone_status, $vnet_status, $fabric_status) = PVE::Network::SDN::status();
foreach my $id (keys %{$vnet_status}) {
if ($vnet_status->{$id}->{zone} eq $zoneid) {
diff --git a/src/PVE/API2/Network/SDN/Zones/Status.pm b/src/PVE/API2/Network/SDN/Zones/Status.pm
index 495756795f47..f9e79ffcef7a 100644
--- a/src/PVE/API2/Network/SDN/Zones/Status.pm
+++ b/src/PVE/API2/Network/SDN/Zones/Status.pm
@@ -63,7 +63,7 @@ __PACKAGE__->register_method({
my $res = [];
- my ($zone_status, $vnet_status) = PVE::Network::SDN::status();
+ my ($zone_status, $vnet_status, $fabric_status) = PVE::Network::SDN::status();
foreach my $id (sort keys %{$zone_status}) {
my $item->{zone} = $id;
diff --git a/src/PVE/Network/SDN.pm b/src/PVE/Network/SDN.pm
index 83f2cc71845e..19efde8b6b95 100644
--- a/src/PVE/Network/SDN.pm
+++ b/src/PVE/Network/SDN.pm
@@ -16,6 +16,8 @@ use PVE::RESTEnvironment qw(log_warn);
use PVE::RPCEnvironment;
use PVE::Tools qw(file_get_contents file_set_contents extract_param dir_glob_regex run_command);
+use PVE::RS::SDN::Fabrics;
+
use PVE::Network::SDN::Vnets;
use PVE::Network::SDN::Zones;
use PVE::Network::SDN::Controllers;
@@ -87,9 +89,9 @@ sub ifquery_check {
}
sub status {
-
my ($zone_status, $vnet_status) = PVE::Network::SDN::Zones::status();
- return ($zone_status, $vnet_status);
+ my $fabric_status = PVE::RS::SDN::Fabrics::status();
+ return ($zone_status, $vnet_status, $fabric_status);
}
sub running_config {
diff --git a/src/test/debug/statuscheck.pl b/src/test/debug/statuscheck.pl
index e43003ba8774..e963117659d8 100644
--- a/src/test/debug/statuscheck.pl
+++ b/src/test/debug/statuscheck.pl
@@ -3,7 +3,8 @@ use warnings;
use PVE::Network::SDN;
use Data::Dumper;
-my ($transport_status, $vnet_status) = PVE::Network::SDN::status();
+my ($transport_status, $vnet_status, $fabric_status) = PVE::Network::SDN::status();
+print Dumper($fabric_status);
print Dumper($vnet_status);
print Dumper($transport_status);
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
* [pve-devel] [PATCH pve-network v2 2/3] fabrics: add api endpoint to return fabrics routes
2025-08-22 9:00 [pve-devel] [PATCH manager/network/proxmox{-ve-rs, -perl-rs} v2 00/12] Add fabric status view Gabriel Goller
` (6 preceding siblings ...)
2025-08-22 9:00 ` [pve-devel] [PATCH pve-network v2 1/3] fabrics: add fabrics status to SDN::status function Gabriel Goller
@ 2025-08-22 9:00 ` Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH pve-network v2 3/3] fabrics: add api endpoint to return fabric neighbors Gabriel Goller
` (4 subsequent siblings)
12 siblings, 0 replies; 25+ messages in thread
From: Gabriel Goller @ 2025-08-22 9:00 UTC (permalink / raw)
To: pve-devel
Add api endpoint that returns all the routes distributed through the
fabrics.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
src/PVE/API2/Network/SDN/Fabrics.pm | 61 ++++++++++++++++++++++++++++-
1 file changed, 60 insertions(+), 1 deletion(-)
diff --git a/src/PVE/API2/Network/SDN/Fabrics.pm b/src/PVE/API2/Network/SDN/Fabrics.pm
index 5644fbee0fff..94905e865ce1 100644
--- a/src/PVE/API2/Network/SDN/Fabrics.pm
+++ b/src/PVE/API2/Network/SDN/Fabrics.pm
@@ -12,6 +12,7 @@ use PVE::API2::Network::SDN::Fabrics::Fabric;
use PVE::API2::Network::SDN::Fabrics::Node;
use PVE::RESTHandler;
+use PVE::JSONSchema qw(get_standard_option);
use base qw(PVE::RESTHandler);
__PACKAGE__->register_method({
@@ -49,7 +50,10 @@ __PACKAGE__->register_method({
my ($param) = @_;
my $res = [
- { subdir => 'fabric' }, { subdir => 'node' }, { subdir => 'all' },
+ { subdir => 'fabric' },
+ { subdir => 'node' },
+ { subdir => 'all' },
+ { subdir => 'routes' },
];
return $res;
@@ -175,4 +179,59 @@ __PACKAGE__->register_method({
},
});
+__PACKAGE__->register_method({
+ name => 'routes',
+ path => 'routes',
+ method => 'GET',
+ description => "Get routes of all fabrics.",
+ permissions => {
+ description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate'",
+ user => 'all',
+ },
+ protected => 1,
+ proxyto => 'node',
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ },
+ },
+ returns => {
+ type => 'array',
+ items => {
+ type => "object",
+ properties => {
+ fabric_id => get_standard_option('pve-sdn-fabric-id'),
+ protocol => get_standard_option('pve-sdn-fabric-protocol'),
+ route => {
+ description => "Route",
+ type => 'string',
+ },
+ via => {
+ description => "Nexthop",
+ type => 'string',
+ },
+ },
+ },
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $rpcenv = PVE::RPCEnvironment::get();
+ my $authuser = $rpcenv->get_user();
+
+ my $res = [];
+
+ my $routes = PVE::RS::SDN::Fabrics::routes();
+ my $fabric_privs = ['SDN.Audit', 'SDN.Allocate'];
+ for my $route (@$routes) {
+ my $fabric_id = $route->{fabric_id};
+ next if !$rpcenv->check_any($authuser, "/sdn/fabrics/$fabric_id", $fabric_privs, 1);
+ push @$res, $route;
+ }
+
+ return $res;
+ },
+});
+
1;
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
* [pve-devel] [PATCH pve-network v2 3/3] fabrics: add api endpoint to return fabric neighbors
2025-08-22 9:00 [pve-devel] [PATCH manager/network/proxmox{-ve-rs, -perl-rs} v2 00/12] Add fabric status view Gabriel Goller
` (7 preceding siblings ...)
2025-08-22 9:00 ` [pve-devel] [PATCH pve-network v2 2/3] fabrics: add api endpoint to return fabrics routes Gabriel Goller
@ 2025-08-22 9:00 ` Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH pve-manager v2 1/3] pvestatd: add fabrics status to pvestatd Gabriel Goller
` (3 subsequent siblings)
12 siblings, 0 replies; 25+ messages in thread
From: Gabriel Goller @ 2025-08-22 9:00 UTC (permalink / raw)
To: pve-devel
Add api endpoint that returns all the fabric neighbors of the current
node.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
src/PVE/API2/Network/SDN/Fabrics.pm | 56 +++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/src/PVE/API2/Network/SDN/Fabrics.pm b/src/PVE/API2/Network/SDN/Fabrics.pm
index 94905e865ce1..65666b69cd9e 100644
--- a/src/PVE/API2/Network/SDN/Fabrics.pm
+++ b/src/PVE/API2/Network/SDN/Fabrics.pm
@@ -54,6 +54,7 @@ __PACKAGE__->register_method({
{ subdir => 'node' },
{ subdir => 'all' },
{ subdir => 'routes' },
+ { subdir => 'neighbors' },
];
return $res;
@@ -234,4 +235,59 @@ __PACKAGE__->register_method({
},
});
+__PACKAGE__->register_method({
+ name => 'neighbors',
+ path => 'neighbors',
+ method => 'GET',
+ description => "Get neighbors of all fabrics.",
+ permissions => {
+ description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate'",
+ user => 'all',
+ },
+ protected => 1,
+ proxyto => 'node',
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ },
+ },
+ returns => {
+ type => 'array',
+ items => {
+ type => "object",
+ properties => {
+ fabric_id => get_standard_option('pve-sdn-fabric-id'),
+ protocol => get_standard_option('pve-sdn-fabric-protocol'),
+ neighbor => {
+ description => "Neighbor",
+ type => 'string',
+ },
+ status => {
+ description => "Status",
+ type => 'string',
+ },
+ },
+ },
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $rpcenv = PVE::RPCEnvironment::get();
+ my $authuser = $rpcenv->get_user();
+
+ my $res = [];
+
+ my $neighbors = PVE::RS::SDN::Fabrics::neighbors();
+ my $fabric_privs = ['SDN.Audit', 'SDN.Allocate'];
+ for my $neighbor (@$neighbors) {
+ my $fabric_id = $neighbor->{fabric_id};
+ next if !$rpcenv->check_any($authuser, "/sdn/fabrics/$fabric_id", $fabric_privs, 1);
+ push @$res, $neighbor;
+ }
+
+ return $res;
+ },
+});
+
1;
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
* [pve-devel] [PATCH pve-manager v2 1/3] pvestatd: add fabrics status to pvestatd
2025-08-22 9:00 [pve-devel] [PATCH manager/network/proxmox{-ve-rs, -perl-rs} v2 00/12] Add fabric status view Gabriel Goller
` (8 preceding siblings ...)
2025-08-22 9:00 ` [pve-devel] [PATCH pve-network v2 3/3] fabrics: add api endpoint to return fabric neighbors Gabriel Goller
@ 2025-08-22 9:00 ` Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH pve-manager v2 2/3] fabrics: add resource view for fabrics Gabriel Goller
` (2 subsequent siblings)
12 siblings, 0 replies; 25+ messages in thread
From: Gabriel Goller @ 2025-08-22 9:00 UTC (permalink / raw)
To: pve-devel
Add the fabric status returned by SDN::status to the pvestatd resources.
This makes the fabrics visible in the SDN resources and in the node
resources. Previously the only SDN entity to be added where the SDN
zones, so a bit of restructuring is needed:
* we also return (and add to the resources) a `sdn_type`, which shows
which type of sdn entity this is (e.g. fabric, zone, etc.).
* we return/add to resources the `protocol` for fabrics as well, so in
the future we can do protocol-specific views.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
PVE/API2/Cluster.pm | 73 +++++++++++++++++++++++++++++++++--------
PVE/Service/pvestatd.pm | 12 ++++---
2 files changed, 68 insertions(+), 17 deletions(-)
diff --git a/PVE/API2/Cluster.pm b/PVE/API2/Cluster.pm
index 02a7ceffee02..c22f8d4fb0e1 100644
--- a/PVE/API2/Cluster.pm
+++ b/PVE/API2/Cluster.pm
@@ -423,6 +423,21 @@ __PACKAGE__->register_method({
optional => 1,
default => 0,
},
+ sdn => {
+ description => "The name of the sdn entity.",
+ type => "string",
+ optional => 1,
+ },
+ sdn_type => {
+ description => "The protocol if this item is a SDN fabric.",
+ type => "string",
+ optional => 1,
+ },
+ protocol => {
+ description => "The protocol if this item is a SDN fabric.",
+ type => "string",
+ optional => 1,
+ },
},
},
},
@@ -585,6 +600,8 @@ __PACKAGE__->register_method({
node => $node,
type => 'sdn',
status => 'ok',
+ # in the UI we want to show the localnetwork as a zone
+ sdn_type => 'zone',
};
push @$res, $local_sdn;
}
@@ -594,19 +611,49 @@ __PACKAGE__->register_method({
my $nodes = PVE::Cluster::get_node_kv("sdn");
for my $node (sort keys %{$nodes}) {
- my $sdns = decode_json($nodes->{$node});
-
- for my $id (sort keys %{$sdns}) {
- next if !$rpcenv->check($authuser, "/sdn/zones/$id", ['SDN.Audit'], 1);
- my $sdn = $sdns->{$id};
- my $entry = {
- id => "sdn/$node/$id",
- sdn => $id,
- node => $node,
- type => 'sdn',
- status => $sdn->{'status'},
- };
- push @$res, $entry;
+ my $node_config = decode_json($nodes->{$node});
+
+ # iterate through all sdn item types (vnet, zone, fabric, etc.)
+ for my $item_type (sort keys %{$node_config}) {
+ # the configuration of a sdn item type (all zones, all fabrics, etc.)
+ my $type_config = $node_config->{$item_type};
+ for my $id (sort keys %{$type_config}) {
+ my $status = $type_config->{$id};
+
+ my $new_entry = {
+ "sdn" => $id,
+ "node" => $node,
+ "type" => 'sdn',
+ "sdn_type" => $item_type,
+ "status" => $status->{status},
+ };
+
+ if ($item_type eq "zone") {
+ next
+ if !$rpcenv->check($authuser, "/sdn/zones/$id", ['SDN.Audit'],
+ 1);
+
+ $new_entry->{id} = "sdn/$node/$item_type/$id";
+ push @$res, $new_entry;
+ } elsif ($item_type eq "fabric") {
+ next
+ if !$rpcenv->check_any(
+ $authuser,
+ "/sdn/fabrics/$id",
+ ['SDN.Audit', 'SDN.Allocate'],
+ 1,
+ );
+
+ my $protocol = $status->{protocol};
+ $new_entry->{id} = "sdn/$node/$item_type/$protocol/$id";
+ $new_entry->{protocol} = $protocol;
+ push @$res, $new_entry;
+ } else {
+ # if the sdn type is not zones or fabric, just add it
+ $new_entry->{id} = "sdn/$node/$item_type/$id";
+ push @$res, $new_entry;
+ }
+ }
}
}
}
diff --git a/PVE/Service/pvestatd.pm b/PVE/Service/pvestatd.pm
index 618d6139af3e..507df4f6c475 100755
--- a/PVE/Service/pvestatd.pm
+++ b/PVE/Service/pvestatd.pm
@@ -766,12 +766,16 @@ sub update_ceph_metadata {
}
sub update_sdn_status {
-
if ($have_sdn) {
- my ($transport_status, $vnet_status) = PVE::Network::SDN::status();
+ my ($zone_status, $vnet_status, $fabric_status) = PVE::Network::SDN::status();
+
+ my $status = {};
+ $status->{zone} = $zone_status;
+ # don't include vnet status, as we don't have a UI panel to show infos for it
+ #$status->{vnet} = $vnet_status;
+ $status->{fabric} = $fabric_status;
- my $status = $transport_status ? encode_json($transport_status) : undef;
- PVE::Cluster::broadcast_node_kv("sdn", $status);
+ PVE::Cluster::broadcast_node_kv("sdn", encode_json($status));
}
}
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
* [pve-devel] [PATCH pve-manager v2 2/3] fabrics: add resource view for fabrics
2025-08-22 9:00 [pve-devel] [PATCH manager/network/proxmox{-ve-rs, -perl-rs} v2 00/12] Add fabric status view Gabriel Goller
` (9 preceding siblings ...)
2025-08-22 9:00 ` [pve-devel] [PATCH pve-manager v2 1/3] pvestatd: add fabrics status to pvestatd Gabriel Goller
@ 2025-08-22 9:00 ` Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH pve-manager v2 3/3] permissions: differentiate between zone and fabric paths Gabriel Goller
2025-08-26 9:52 ` [pve-devel] [PATCH manager/network/proxmox{-ve-rs, -perl-rs} v2 00/12] Add fabric status view Gabriel Goller
12 siblings, 0 replies; 25+ messages in thread
From: Gabriel Goller @ 2025-08-22 9:00 UTC (permalink / raw)
To: pve-devel
When clicking on the fabric resources a new content view is available.
It shows the routes and the neighbors of the fabric on that specific
node.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
www/manager6/Makefile | 1 +
www/manager6/sdn/Browser.js | 120 ++++++++++++++++++++-----
www/manager6/sdn/FabricsContentView.js | 91 +++++++++++++++++++
www/manager6/sdn/StatusView.js | 2 +-
4 files changed, 191 insertions(+), 23 deletions(-)
create mode 100644 www/manager6/sdn/FabricsContentView.js
diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 07401f21520b..8b4d672f5145 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -313,6 +313,7 @@ JSSRC= \
sdn/zones/VlanEdit.js \
sdn/zones/VxlanEdit.js \
sdn/FabricsView.js \
+ sdn/FabricsContentView.js \
sdn/fabrics/Common.js \
sdn/fabrics/InterfacePanel.js \
sdn/fabrics/NodeEdit.js \
diff --git a/www/manager6/sdn/Browser.js b/www/manager6/sdn/Browser.js
index f7694ae91864..af82bd6390d3 100644
--- a/www/manager6/sdn/Browser.js
+++ b/www/manager6/sdn/Browser.js
@@ -15,39 +15,115 @@ Ext.define('PVE.sdn.Browser', {
if (!sdnId) {
throw 'no sdn ID specified';
}
+ let sdnType = me.pveSelNode.data.sdn_type;
+ if (!sdnType) {
+ throw 'no sdn object type specified';
+ }
me.items = [];
+ const caps = Ext.state.Manager.get('GuiCap');
+
+ switch (sdnType) {
+ case 'zone':
+ me.items.push({
+ nodename: nodename,
+ zone: sdnId,
+ xtype: 'pveSDNZoneContentPanel',
+ title: gettext('Content'),
+ iconCls: 'fa fa-th',
+ itemId: 'content',
+ });
+
+ if (caps.sdn['Permissions.Modify']) {
+ me.items.push({
+ xtype: 'pveACLView',
+ title: gettext('Permissions'),
+ iconCls: 'fa fa-unlock',
+ itemId: 'permissions',
+ path: `/sdn/zones/${sdnId}`,
+ });
+ }
+ break;
+ case 'fabric':
+ {
+ let neighborStore = new Ext.data.Store({
+ model: 'Neighbor',
+ proxy: {
+ type: 'proxmox',
+ url: '/api2/json/cluster/sdn/fabrics/neighbors',
+ reader: {
+ type: 'json',
+ rootProperty: 'data',
+ },
+ extraParams: {
+ node: nodename,
+ },
+ },
+ autoLoad: true,
+ });
+
+ let routeStore = new Ext.data.Store({
+ model: 'Route',
+ proxy: {
+ type: 'proxmox',
+ url: '/api2/json/cluster/sdn/fabrics/routes',
+ reader: {
+ type: 'json',
+ rootProperty: 'data',
+ },
+ extraParams: {
+ node: nodename,
+ },
+ },
+ autoLoad: true,
+ });
+
+ me.items.push({
+ nodename: nodename,
+ routeStore: routeStore,
+ fabricId: sdnId,
+ protocol: me.pveSelNode.data.protocol,
+ xtype: 'pveSDNFabricRoutesContentView',
+ title: gettext('Routes'),
+ iconCls: 'fa fa-th',
+ itemId: 'routes',
+ width: '100%',
+ });
+ me.items.push({
+ nodename: nodename,
+ neighborStore: neighborStore,
+ fabricId: sdnId,
+ protocol: me.pveSelNode.data.protocol,
+ xtype: 'pveSDNFabricNeighborsContentView',
+ title: gettext('Neighbors'),
+ iconCls: 'fa fa-th',
+ itemId: 'neighbors',
+ width: '100%',
+ });
+ }
+ break;
+ }
+
Ext.apply(me, {
title: Ext.String.format(
- gettext('Zone {0} on node {1}'),
+ gettext('{0} {1} on node {2}'),
+ `${sdnType}`,
`'${sdnId}'`,
`'${nodename}'`,
),
hstateid: 'sdntab',
});
- const caps = Ext.state.Manager.get('GuiCap');
-
- me.items.push({
- nodename: nodename,
- zone: sdnId,
- xtype: 'pveSDNZoneContentPanel',
- title: gettext('Content'),
- iconCls: 'fa fa-th',
- itemId: 'content',
- });
-
- if (caps.sdn['Permissions.Modify']) {
- me.items.push({
- xtype: 'pveACLView',
- title: gettext('Permissions'),
- iconCls: 'fa fa-unlock',
- itemId: 'permissions',
- path: `/sdn/zones/${sdnId}`,
- });
- }
-
me.callParent();
},
});
+
+Ext.define('Route', {
+ extend: 'Ext.data.Model',
+ fields: ['route', 'via', 'fabric_id', 'protocol'],
+});
+Ext.define('Neighbor', {
+ extend: 'Ext.data.Model',
+ fields: ['neighbor', 'status', 'fabric_id', 'protocol'],
+});
diff --git a/www/manager6/sdn/FabricsContentView.js b/www/manager6/sdn/FabricsContentView.js
new file mode 100644
index 000000000000..f1e5ec146b8b
--- /dev/null
+++ b/www/manager6/sdn/FabricsContentView.js
@@ -0,0 +1,91 @@
+Ext.define('PVE.sdn.FabricRoutesContentView', {
+ extend: 'Ext.grid.GridPanel',
+ alias: 'widget.pveSDNFabricRoutesContentView',
+
+ initComponent: function () {
+ let me = this;
+ let sm = Ext.create('Ext.selection.RowModel', {});
+
+ me.routeStore.addFilter([
+ {
+ property: 'fabric_id',
+ value: me.fabricId,
+ },
+ {
+ property: 'protocol',
+ value: me.protocol,
+ },
+ ]);
+ me.routeStore.sort('route', 'ASC');
+
+ Ext.apply(me, {
+ store: me.routeStore,
+ selModel: sm,
+ columns: [
+ {
+ header: 'Route',
+ sortable: true,
+ dataIndex: 'route',
+ flex: 1,
+ },
+ {
+ header: 'Via',
+ sortable: true,
+ dataIndex: 'via',
+ renderer: (value) => {
+ if (Ext.isArray(value)) {
+ return value.join('<br>');
+ }
+ return value || '';
+ },
+ flex: 1,
+ },
+ ],
+ });
+
+ me.callParent();
+ },
+});
+
+Ext.define('PVE.sdn.FabricNeighborsContentView', {
+ extend: 'Ext.grid.GridPanel',
+ alias: 'widget.pveSDNFabricNeighborsContentView',
+
+ initComponent: function () {
+ let me = this;
+ let sm = Ext.create('Ext.selection.RowModel', {});
+
+ me.neighborStore.addFilter([
+ {
+ property: 'fabric_id',
+ value: me.fabricId,
+ },
+ {
+ property: 'protocol',
+ value: me.protocol,
+ },
+ ]);
+ me.neighborStore.sort('neighbor', 'ASC');
+
+ Ext.apply(me, {
+ store: me.neighborStore,
+ selModel: sm,
+ columns: [
+ {
+ header: 'Neighbor',
+ sortable: true,
+ dataIndex: 'neighbor',
+ flex: 1,
+ },
+ {
+ header: 'Status',
+ sortable: true,
+ dataIndex: 'status',
+ flex: 0.5,
+ },
+ ],
+ });
+
+ me.callParent();
+ },
+});
diff --git a/www/manager6/sdn/StatusView.js b/www/manager6/sdn/StatusView.js
index dd05c73fdfcf..e66e3f624354 100644
--- a/www/manager6/sdn/StatusView.js
+++ b/www/manager6/sdn/StatusView.js
@@ -102,7 +102,7 @@ Ext.define(
function () {
Ext.define('pve-sdn-status', {
extend: 'Ext.data.Model',
- fields: ['id', 'type', 'node', 'status', 'sdn'],
+ fields: ['id', 'type', 'node', 'status', 'sdn', 'sdn_type'],
idProperty: 'id',
});
},
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
* [pve-devel] [PATCH pve-manager v2 3/3] permissions: differentiate between zone and fabric paths
2025-08-22 9:00 [pve-devel] [PATCH manager/network/proxmox{-ve-rs, -perl-rs} v2 00/12] Add fabric status view Gabriel Goller
` (10 preceding siblings ...)
2025-08-22 9:00 ` [pve-devel] [PATCH pve-manager v2 2/3] fabrics: add resource view for fabrics Gabriel Goller
@ 2025-08-22 9:00 ` Gabriel Goller
2025-08-26 9:52 ` [pve-devel] [PATCH manager/network/proxmox{-ve-rs, -perl-rs} v2 00/12] Add fabric status view Gabriel Goller
12 siblings, 0 replies; 25+ messages in thread
From: Gabriel Goller @ 2025-08-22 9:00 UTC (permalink / raw)
To: pve-devel
With the addition of the fabrics status in the resources, there are
fabrics and zones using the 'sdn' type. The finer distinction between
zones and fabrics is done using the new 'sdn_type' parameter.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
www/manager6/data/PermPathStore.js | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/www/manager6/data/PermPathStore.js b/www/manager6/data/PermPathStore.js
index c7ec42314c82..8a5344483f0d 100644
--- a/www/manager6/data/PermPathStore.js
+++ b/www/manager6/data/PermPathStore.js
@@ -43,7 +43,14 @@ Ext.define('PVE.data.PermPathStore', {
path = '/vms/' + record.get('vmid');
break;
case 'sdn':
- path = '/sdn/zones/' + record.get('sdn');
+ switch (record.get('sdn_type')) {
+ case 'zone':
+ path = '/sdn/zones/' + record.get('sdn');
+ break;
+ case 'fabric':
+ path = '/sdn/fabrics/' + record.get('sdn');
+ break;
+ }
break;
case 'storage':
path = '/storage/' + record.get('storage');
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [pve-devel] [PATCH proxmox-perl-rs v2 2/4] fabrics: add function to get status of fabric
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-perl-rs v2 2/4] fabrics: add function to get status of fabric Gabriel Goller
@ 2025-08-25 8:11 ` Wolfgang Bumiller
2025-08-25 8:25 ` Wolfgang Bumiller
2025-08-25 11:39 ` Gabriel Goller
0 siblings, 2 replies; 25+ messages in thread
From: Wolfgang Bumiller @ 2025-08-25 8:11 UTC (permalink / raw)
To: Gabriel Goller; +Cc: pve-devel
On Fri, Aug 22, 2025 at 11:00:36AM +0200, Gabriel Goller wrote:
> Add a function to get the status of a fabric. This is the status which
> will then be inserted into the pvestatd daemon and returned through the
> resources api. In order the generate the HashMap of statuses for all
> fabrics we need to read the fabric config and execute a vtysh (frr)
> command to get the routes of the corresponding fabric. If there is at
> least one route which is related to the fabric, the fabric is considered
> "ok".
>
> Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
> ---
> pve-rs/src/bindings/sdn/fabrics.rs | 194 +++++++++++++++++++++++++++++
> 1 file changed, 194 insertions(+)
>
> diff --git a/pve-rs/src/bindings/sdn/fabrics.rs b/pve-rs/src/bindings/sdn/fabrics.rs
> index 1dc8bf4320e6..3f70d421e582 100644
> --- a/pve-rs/src/bindings/sdn/fabrics.rs
> +++ b/pve-rs/src/bindings/sdn/fabrics.rs
> @@ -9,8 +9,10 @@ pub mod pve_rs_sdn_fabrics {
> use std::fmt::Write;
> use std::net::IpAddr;
> use std::ops::Deref;
> + use std::process::Command;
> use std::sync::Mutex;
>
> + use anyhow::Context;
> use anyhow::Error;
> use openssl::hash::{MessageDigest, hash};
> use serde::{Deserialize, Serialize};
> @@ -578,4 +580,196 @@ pub mod pve_rs_sdn_fabrics {
>
> Ok(interfaces)
> }
> +
> + /// This module contains status-related structs that represent Routes and Neighbors for all
> + /// protocols
> + pub mod status {
^ This seems to be a helper module which does not contain any
perlmod/perl specifics.
I'd argue it's time to start a `crate::sdn` module outside of the
`bindings` submodule for this.
The `bindings` module should become rather more lean in the future and
focus mostly on the perl/rust interaction.
> + use std::collections::{HashMap, HashSet};
> +
> + use serde::Serialize;
> +
> + use proxmox_frr::de::{self};
> + use proxmox_ve_config::sdn::fabric::{
> + FabricConfig,
> + section_config::{fabric::FabricId, node::Node as ConfigNode},
> + };
> +
> + /// Protocol
> + #[derive(Debug, Serialize, Clone, Copy)]
> + pub enum Protocol {
> + /// Openfabric
> + Openfabric,
> + /// OSPF
> + Ospf,
> + }
> +
> + /// The status of a fabric.
> + #[derive(Debug, Serialize)]
> + pub enum FabricStatus {
> + /// The fabric exists and has a route
> + #[serde(rename = "ok")]
> + Ok,
> + /// The fabric does not exist or doesn't distribute any routes
> + #[serde(rename = "not ok")]
> + NotOk,
> + }
> +
> + /// Status of a fabric.
> + ///
> + /// Check if there are any routes, if yes, then the status is ok, otherwise not ok.
^ Not sure how this describes the *struct*, though ;-)
> + #[derive(Debug, Serialize)]
> + pub struct Status {
> + #[serde(rename = "type")]
> + ty: String,
> + status: FabricStatus,
> + protocol: Protocol,
> + sdn: FabricId,
> + sdn_type: String,
> + }
> +
> + /// Parsed routes for all protocols
> + ///
> + /// These are the routes parsed from the json output of:
> + /// `vtysh -c 'show ip route <protocol> json'`.
> + #[derive(Debug, Serialize)]
> + pub struct RoutesParsed {
> + /// All openfabric routes in FRR
> + pub openfabric: de::Routes,
> + /// All ospf routes in FRR
> + pub ospf: de::Routes,
> + }
> +
> + impl TryInto<HashMap<FabricId, Status>> for RoutesParsed {
> + type Error = anyhow::Error;
> +
> + fn try_into(self) -> Result<HashMap<FabricId, Status>, 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")?;
^ I'm really not a fan of doing file I/O in a TryInto implementation.
These are still supposed to be "simple"[1].
Better make this a method.
[1] https://doc.rust-lang.org/std/convert/trait.TryFrom.html
> + let config = FabricConfig::parse_section_config(&raw_config)?;
> +
> + let mut stats: HashMap<FabricId, Status> = HashMap::new();
> +
> + for (nodeid, node) in config.values().flat_map(|entry| {
> + entry
> + .nodes()
> + .map(|(id, node)| (id.to_string(), node.clone()))
^ Unnecessary `.to_string()` and `.clone()` - the entire `.map()`
line can be dropped if use `if nodeid.as_str() != hostname` below.
> + }) {
> + if nodeid != hostname {
> + continue;
> + }
> + let fabric_id = node.id().fabric_id().clone();
> +
> + let current_protocol = match &node {
> + ConfigNode::Openfabric(_) => Protocol::Openfabric,
> + ConfigNode::Ospf(_) => Protocol::Ospf,
> + };
> +
> + let mut all_routes = HashMap::new();
> + match &node {
> + ConfigNode::Openfabric(_) => all_routes.extend(&self.openfabric.0),
> + ConfigNode::Ospf(_) => all_routes.extend(&self.ospf.0),
> + }
> +
> + // get interfaces
> + let interface_names: HashSet<String> = match node {
This also doesn't need to allocate the string - use &str
> + ConfigNode::Openfabric(n) => n
> + .properties()
> + .interfaces()
> + .map(|i| i.name().to_string())
^ with .as_str() here
> + .collect(),
> + ConfigNode::Ospf(n) => n
> + .properties()
> + .interfaces()
> + .map(|i| i.name().to_string())
^ and here
> + .collect(),
> + };
> +
> + // determine status by checking if any routes exist for our interfaces
> + let has_routes = all_routes.iter().any(|(_, v)| {
^ Could use
.values().any(|v| {
(no need to `_` the key)
> + v.iter().any(|route| {
> + route
> + .nexthops
> + .iter()
> + .any(|nexthop| interface_names.contains(&nexthop.interface_name))
and on .interface_name here
> + })
> + });
> +
> + let fabric = Status {
> + ty: "sdn".to_owned(),
> + status: if has_routes {
> + FabricStatus::Ok
> + } else {
> + FabricStatus::NotOk
> + },
> + sdn_type: "fabric".to_string(),
> + protocol: current_protocol,
> + sdn: fabric_id.clone(),
> + };
> + stats.insert(fabric_id, fabric);
> + }
> +
> + Ok(stats)
> + }
> + }
> + }
> +
> + /// 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.
> + /// Check if there are any routes in the routing table that use the interface specified in the
> + /// config. If there are, show "ok" as status, otherwise "not ok".
> + #[export]
> + fn status() -> Result<HashMap<FabricId, status::Status>, 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()
> + }
> }
> --
> 2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [pve-devel] [PATCH proxmox-perl-rs v2 3/4] fabrics: add function to get all routes distributed by the fabrics
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-perl-rs v2 3/4] fabrics: add function to get all routes distributed by the fabrics Gabriel Goller
@ 2025-08-25 8:22 ` Wolfgang Bumiller
2025-08-25 11:40 ` Gabriel Goller
0 siblings, 1 reply; 25+ messages in thread
From: Wolfgang Bumiller @ 2025-08-25 8:22 UTC (permalink / raw)
To: Gabriel Goller; +Cc: pve-devel
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@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
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [pve-devel] [PATCH proxmox-perl-rs v2 2/4] fabrics: add function to get status of fabric
2025-08-25 8:11 ` Wolfgang Bumiller
@ 2025-08-25 8:25 ` Wolfgang Bumiller
2025-08-25 11:39 ` Gabriel Goller
1 sibling, 0 replies; 25+ messages in thread
From: Wolfgang Bumiller @ 2025-08-25 8:25 UTC (permalink / raw)
To: Gabriel Goller; +Cc: pve-devel
On Mon, Aug 25, 2025 at 10:11:01AM +0200, Wolfgang Bumiller wrote:
> On Fri, Aug 22, 2025 at 11:00:36AM +0200, Gabriel Goller wrote:
> > Add a function to get the status of a fabric. This is the status which
> > will then be inserted into the pvestatd daemon and returned through the
> > resources api. In order the generate the HashMap of statuses for all
> > fabrics we need to read the fabric config and execute a vtysh (frr)
> > command to get the routes of the corresponding fabric. If there is at
> > least one route which is related to the fabric, the fabric is considered
> > "ok".
> >
> > Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
> > ---
> > pve-rs/src/bindings/sdn/fabrics.rs | 194 +++++++++++++++++++++++++++++
> > 1 file changed, 194 insertions(+)
> >
> > diff --git a/pve-rs/src/bindings/sdn/fabrics.rs b/pve-rs/src/bindings/sdn/fabrics.rs
> > index 1dc8bf4320e6..3f70d421e582 100644
> > --- a/pve-rs/src/bindings/sdn/fabrics.rs
> > +++ b/pve-rs/src/bindings/sdn/fabrics.rs
> > @@ -9,8 +9,10 @@ pub mod pve_rs_sdn_fabrics {
> > use std::fmt::Write;
> > use std::net::IpAddr;
> > use std::ops::Deref;
> > + use std::process::Command;
> > use std::sync::Mutex;
> >
> > + use anyhow::Context;
> > use anyhow::Error;
> > use openssl::hash::{MessageDigest, hash};
> > use serde::{Deserialize, Serialize};
> > @@ -578,4 +580,196 @@ pub mod pve_rs_sdn_fabrics {
> >
> > Ok(interfaces)
> > }
> > +
> > + /// This module contains status-related structs that represent Routes and Neighbors for all
> > + /// protocols
> > + pub mod status {
>
> ^ This seems to be a helper module which does not contain any
> perlmod/perl specifics.
> I'd argue it's time to start a `crate::sdn` module outside of the
> `bindings` submodule for this.
>
> The `bindings` module should become rather more lean in the future and
> focus mostly on the perl/rust interaction.
>
> > + use std::collections::{HashMap, HashSet};
> > +
> > + use serde::Serialize;
> > +
> > + use proxmox_frr::de::{self};
> > + use proxmox_ve_config::sdn::fabric::{
> > + FabricConfig,
> > + section_config::{fabric::FabricId, node::Node as ConfigNode},
> > + };
> > +
> > + /// Protocol
> > + #[derive(Debug, Serialize, Clone, Copy)]
> > + pub enum Protocol {
> > + /// Openfabric
> > + Openfabric,
> > + /// OSPF
> > + Ospf,
> > + }
> > +
> > + /// The status of a fabric.
> > + #[derive(Debug, Serialize)]
> > + pub enum FabricStatus {
> > + /// The fabric exists and has a route
> > + #[serde(rename = "ok")]
> > + Ok,
> > + /// The fabric does not exist or doesn't distribute any routes
> > + #[serde(rename = "not ok")]
> > + NotOk,
> > + }
> > +
> > + /// Status of a fabric.
> > + ///
> > + /// Check if there are any routes, if yes, then the status is ok, otherwise not ok.
>
> ^ Not sure how this describes the *struct*, though ;-)
>
> > + #[derive(Debug, Serialize)]
> > + pub struct Status {
> > + #[serde(rename = "type")]
> > + ty: String,
> > + status: FabricStatus,
> > + protocol: Protocol,
> > + sdn: FabricId,
> > + sdn_type: String,
^ If we already have serialization helpers, this could be an enum to
avoid some more string allocations (or use `&'static str`, but an enum
is safer...)
> > + }
> > +
> > + /// Parsed routes for all protocols
> > + ///
> > + /// These are the routes parsed from the json output of:
> > + /// `vtysh -c 'show ip route <protocol> json'`.
> > + #[derive(Debug, Serialize)]
> > + pub struct RoutesParsed {
> > + /// All openfabric routes in FRR
> > + pub openfabric: de::Routes,
> > + /// All ospf routes in FRR
> > + pub ospf: de::Routes,
> > + }
> > +
> > + impl TryInto<HashMap<FabricId, Status>> for RoutesParsed {
> > + type Error = anyhow::Error;
> > +
> > + fn try_into(self) -> Result<HashMap<FabricId, Status>, 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")?;
>
> ^ I'm really not a fan of doing file I/O in a TryInto implementation.
> These are still supposed to be "simple"[1].
>
> Better make this a method.
>
> [1] https://doc.rust-lang.org/std/convert/trait.TryFrom.html
>
> > + let config = FabricConfig::parse_section_config(&raw_config)?;
> > +
> > + let mut stats: HashMap<FabricId, Status> = HashMap::new();
> > +
> > + for (nodeid, node) in config.values().flat_map(|entry| {
> > + entry
> > + .nodes()
> > + .map(|(id, node)| (id.to_string(), node.clone()))
>
> ^ Unnecessary `.to_string()` and `.clone()` - the entire `.map()`
> line can be dropped if use `if nodeid.as_str() != hostname` below.
>
> > + }) {
> > + if nodeid != hostname {
> > + continue;
> > + }
> > + let fabric_id = node.id().fabric_id().clone();
^ same as in patch 3
> > +
> > + let current_protocol = match &node {
> > + ConfigNode::Openfabric(_) => Protocol::Openfabric,
> > + ConfigNode::Ospf(_) => Protocol::Ospf,
> > + };
> > +
> > + let mut all_routes = HashMap::new();
^ same as in patch 3
> > + match &node {
> > + ConfigNode::Openfabric(_) => all_routes.extend(&self.openfabric.0),
> > + ConfigNode::Ospf(_) => all_routes.extend(&self.ospf.0),
> > + }
> > +
> > + // get interfaces
> > + let interface_names: HashSet<String> = match node {
>
> This also doesn't need to allocate the string - use &str
>
> > + ConfigNode::Openfabric(n) => n
> > + .properties()
> > + .interfaces()
> > + .map(|i| i.name().to_string())
>
> ^ with .as_str() here
>
> > + .collect(),
> > + ConfigNode::Ospf(n) => n
> > + .properties()
> > + .interfaces()
> > + .map(|i| i.name().to_string())
>
> ^ and here
>
> > + .collect(),
> > + };
> > +
> > + // determine status by checking if any routes exist for our interfaces
> > + let has_routes = all_routes.iter().any(|(_, v)| {
>
> ^ Could use
>
> .values().any(|v| {
>
> (no need to `_` the key)
>
> > + v.iter().any(|route| {
> > + route
> > + .nexthops
> > + .iter()
> > + .any(|nexthop| interface_names.contains(&nexthop.interface_name))
>
> and on .interface_name here
>
> > + })
> > + });
> > +
> > + let fabric = Status {
> > + ty: "sdn".to_owned(),
> > + status: if has_routes {
> > + FabricStatus::Ok
> > + } else {
> > + FabricStatus::NotOk
> > + },
> > + sdn_type: "fabric".to_string(),
> > + protocol: current_protocol,
> > + sdn: fabric_id.clone(),
> > + };
> > + stats.insert(fabric_id, fabric);
> > + }
> > +
> > + Ok(stats)
> > + }
> > + }
> > + }
> > +
> > + /// 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.
> > + /// Check if there are any routes in the routing table that use the interface specified in the
> > + /// config. If there are, show "ok" as status, otherwise "not ok".
> > + #[export]
> > + fn status() -> Result<HashMap<FabricId, status::Status>, 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()
> > + }
> > }
> > --
> > 2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [pve-devel] [PATCH proxmox-perl-rs v2 4/4] fabrics: add function to get all neighbors of the fabric
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-perl-rs v2 4/4] fabrics: add function to get all neighbors of the fabric Gabriel Goller
@ 2025-08-25 8:28 ` Wolfgang Bumiller
2025-08-25 11:41 ` Gabriel Goller
0 siblings, 1 reply; 25+ messages in thread
From: Wolfgang Bumiller @ 2025-08-25 8:28 UTC (permalink / raw)
To: Gabriel Goller; +Cc: pve-devel
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.goller@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
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [pve-devel] [PATCH proxmox-perl-rs v2 2/4] fabrics: add function to get status of fabric
2025-08-25 8:11 ` Wolfgang Bumiller
2025-08-25 8:25 ` Wolfgang Bumiller
@ 2025-08-25 11:39 ` Gabriel Goller
2025-08-25 14:37 ` Wolfgang Bumiller
1 sibling, 1 reply; 25+ messages in thread
From: Gabriel Goller @ 2025-08-25 11:39 UTC (permalink / raw)
To: Wolfgang Bumiller; +Cc: pve-devel
On 25.08.2025 10:11, Wolfgang Bumiller wrote:
>On Fri, Aug 22, 2025 at 11:00:36AM +0200, Gabriel Goller wrote:
>> Add a function to get the status of a fabric. This is the status which
>> will then be inserted into the pvestatd daemon and returned through the
>> resources api. In order the generate the HashMap of statuses for all
>> fabrics we need to read the fabric config and execute a vtysh (frr)
>> command to get the routes of the corresponding fabric. If there is at
>> least one route which is related to the fabric, the fabric is considered
>> "ok".
>>
>> Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
>> ---
>> pve-rs/src/bindings/sdn/fabrics.rs | 194 +++++++++++++++++++++++++++++
>> 1 file changed, 194 insertions(+)
>>
>> diff --git a/pve-rs/src/bindings/sdn/fabrics.rs b/pve-rs/src/bindings/sdn/fabrics.rs
>> index 1dc8bf4320e6..3f70d421e582 100644
>> --- a/pve-rs/src/bindings/sdn/fabrics.rs
>> +++ b/pve-rs/src/bindings/sdn/fabrics.rs
>> @@ -9,8 +9,10 @@ pub mod pve_rs_sdn_fabrics {
>> use std::fmt::Write;
>> use std::net::IpAddr;
>> use std::ops::Deref;
>> + use std::process::Command;
>> use std::sync::Mutex;
>>
>> + use anyhow::Context;
>> use anyhow::Error;
>> use openssl::hash::{MessageDigest, hash};
>> use serde::{Deserialize, Serialize};
>> @@ -578,4 +580,196 @@ pub mod pve_rs_sdn_fabrics {
>>
>> Ok(interfaces)
>> }
>> +
>> + /// This module contains status-related structs that represent Routes and Neighbors for all
>> + /// protocols
>> + pub mod status {
>
>^ This seems to be a helper module which does not contain any
>perlmod/perl specifics.
>I'd argue it's time to start a `crate::sdn` module outside of the
>`bindings` submodule for this.
>
>The `bindings` module should become rather more lean in the future and
>focus mostly on the perl/rust interaction.
Umm do I understand you correctly that you want to have something like
this:
src/
├─ bindings/
│ ├─ sdn/
│ │ ├─ fabrics.rs
├─ sdn/
│ ├─ status.rs
?
IMO we could move all the status stuff out to
crate::bindings::sdn::status. But I don't know about separating all the
types, conversion methods and actual perl methods -- I'd rather keep all
the perl-facing stuff in the same file.
>> + use std::collections::{HashMap, HashSet};
>> +
>> + use serde::Serialize;
>> +
>> + use proxmox_frr::de::{self};
>> + use proxmox_ve_config::sdn::fabric::{
>> + FabricConfig,
>> + section_config::{fabric::FabricId, node::Node as ConfigNode},
>> + };
>> +
>> + /// Protocol
>> + #[derive(Debug, Serialize, Clone, Copy)]
>> + pub enum Protocol {
>> + /// Openfabric
>> + Openfabric,
>> + /// OSPF
>> + Ospf,
>> + }
>> +
>> + /// The status of a fabric.
>> + #[derive(Debug, Serialize)]
>> + pub enum FabricStatus {
>> + /// The fabric exists and has a route
>> + #[serde(rename = "ok")]
>> + Ok,
>> + /// The fabric does not exist or doesn't distribute any routes
>> + #[serde(rename = "not ok")]
>> + NotOk,
>> + }
>> +
>> + /// Status of a fabric.
>> + ///
>> + /// Check if there are any routes, if yes, then the status is ok, otherwise not ok.
>
>^ Not sure how this describes the *struct*, though ;-)
Oops, this slipped through, should have been somewhere else.
>> + #[derive(Debug, Serialize)]
>> + pub struct Status {
>> + #[serde(rename = "type")]
>> + ty: String,
>> + status: FabricStatus,
>> + protocol: Protocol,
>> + sdn: FabricId,
>> + sdn_type: String,
>> + }
>> +
>> + /// Parsed routes for all protocols
>> + ///
>> + /// These are the routes parsed from the json output of:
>> + /// `vtysh -c 'show ip route <protocol> json'`.
>> + #[derive(Debug, Serialize)]
>> + pub struct RoutesParsed {
>> + /// All openfabric routes in FRR
>> + pub openfabric: de::Routes,
>> + /// All ospf routes in FRR
>> + pub ospf: de::Routes,
>> + }
>> +
>> + impl TryInto<HashMap<FabricId, Status>> for RoutesParsed {
>> + type Error = anyhow::Error;
>> +
>> + fn try_into(self) -> Result<HashMap<FabricId, Status>, 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")?;
>
>^ I'm really not a fan of doing file I/O in a TryInto implementation.
>These are still supposed to be "simple"[1].
>
>Better make this a method.
>
>[1] https://doc.rust-lang.org/std/convert/trait.TryFrom.html
Yup, I agree, changed all the TryInto impls to functions `get_routes`,
`get_neighbors` and `get_status`.
Also fixed all the other stuff below.
Thanks for the review!
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [pve-devel] [PATCH proxmox-perl-rs v2 3/4] fabrics: add function to get all routes distributed by the fabrics
2025-08-25 8:22 ` Wolfgang Bumiller
@ 2025-08-25 11:40 ` Gabriel Goller
0 siblings, 0 replies; 25+ messages in thread
From: Gabriel Goller @ 2025-08-25 11:40 UTC (permalink / raw)
To: Wolfgang Bumiller; +Cc: pve-devel
Fixed all the issues, thanks for the review!
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [pve-devel] [PATCH proxmox-perl-rs v2 4/4] fabrics: add function to get all neighbors of the fabric
2025-08-25 8:28 ` Wolfgang Bumiller
@ 2025-08-25 11:41 ` Gabriel Goller
0 siblings, 0 replies; 25+ messages in thread
From: Gabriel Goller @ 2025-08-25 11:41 UTC (permalink / raw)
To: Wolfgang Bumiller; +Cc: pve-devel
On 25.08.2025 10:28, Wolfgang Bumiller wrote:
>On Fri, Aug 22, 2025 at 11:00:38AM +0200, Gabriel Goller wrote:
>> + 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<...>` ?
Definitely makes sense, added it.
Also fixed all the other issues, thanks for the review!
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [pve-devel] [PATCH proxmox-perl-rs v2 2/4] fabrics: add function to get status of fabric
2025-08-25 11:39 ` Gabriel Goller
@ 2025-08-25 14:37 ` Wolfgang Bumiller
2025-08-25 15:33 ` Gabriel Goller
0 siblings, 1 reply; 25+ messages in thread
From: Wolfgang Bumiller @ 2025-08-25 14:37 UTC (permalink / raw)
To: pve-devel
On Mon, Aug 25, 2025 at 01:39:48PM +0200, Gabriel Goller wrote:
> On 25.08.2025 10:11, Wolfgang Bumiller wrote:
> > On Fri, Aug 22, 2025 at 11:00:36AM +0200, Gabriel Goller wrote:
> > > Add a function to get the status of a fabric. This is the status which
> > > will then be inserted into the pvestatd daemon and returned through the
> > > resources api. In order the generate the HashMap of statuses for all
> > > fabrics we need to read the fabric config and execute a vtysh (frr)
> > > command to get the routes of the corresponding fabric. If there is at
> > > least one route which is related to the fabric, the fabric is considered
> > > "ok".
> > >
> > > Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
> > > ---
> > > pve-rs/src/bindings/sdn/fabrics.rs | 194 +++++++++++++++++++++++++++++
> > > 1 file changed, 194 insertions(+)
> > >
> > > diff --git a/pve-rs/src/bindings/sdn/fabrics.rs b/pve-rs/src/bindings/sdn/fabrics.rs
> > > index 1dc8bf4320e6..3f70d421e582 100644
> > > --- a/pve-rs/src/bindings/sdn/fabrics.rs
> > > +++ b/pve-rs/src/bindings/sdn/fabrics.rs
> > > @@ -9,8 +9,10 @@ pub mod pve_rs_sdn_fabrics {
> > > use std::fmt::Write;
> > > use std::net::IpAddr;
> > > use std::ops::Deref;
> > > + use std::process::Command;
> > > use std::sync::Mutex;
> > >
> > > + use anyhow::Context;
> > > use anyhow::Error;
> > > use openssl::hash::{MessageDigest, hash};
> > > use serde::{Deserialize, Serialize};
> > > @@ -578,4 +580,196 @@ pub mod pve_rs_sdn_fabrics {
> > >
> > > Ok(interfaces)
> > > }
> > > +
> > > + /// This module contains status-related structs that represent Routes and Neighbors for all
> > > + /// protocols
> > > + pub mod status {
> >
> > ^ This seems to be a helper module which does not contain any
> > perlmod/perl specifics.
> > I'd argue it's time to start a `crate::sdn` module outside of the
> > `bindings` submodule for this.
> >
> > The `bindings` module should become rather more lean in the future and
> > focus mostly on the perl/rust interaction.
>
> Umm do I understand you correctly that you want to have something like
> this:
>
> src/
> ├─ bindings/
> │ ├─ sdn/
> │ │ ├─ fabrics.rs
> ├─ sdn/
> │ ├─ status.rs
>
> ?
Yes. The bindings should just be the perl interface and its point is to
provide documentation via rustdoc, and the rustdocs should tell you how
to use it *from perl*.
The rest would be additional code we need to provide the perl interface
for the external crates.
>
> IMO we could move all the status stuff out to
> crate::bindings::sdn::status. But I don't know about separating all the
> types, conversion methods and actual perl methods -- I'd rather keep all
> the perl-facing stuff in the same file.
You don't need to separate everything. My point was that it does *not*
contain perl *binding* specifics. As for being *perl* specific, I mean,
the entire `pve-rs` crate *is* perl specific right now...
>
> > > + use std::collections::{HashMap, HashSet};
> > > +
> > > + use serde::Serialize;
> > > +
> > > + use proxmox_frr::de::{self};
> > > + use proxmox_ve_config::sdn::fabric::{
> > > + FabricConfig,
> > > + section_config::{fabric::FabricId, node::Node as ConfigNode},
> > > + };
> > > +
> > > + /// Protocol
> > > + #[derive(Debug, Serialize, Clone, Copy)]
> > > + pub enum Protocol {
> > > + /// Openfabric
> > > + Openfabric,
> > > + /// OSPF
> > > + Ospf,
> > > + }
> > > +
> > > + /// The status of a fabric.
> > > + #[derive(Debug, Serialize)]
> > > + pub enum FabricStatus {
> > > + /// The fabric exists and has a route
> > > + #[serde(rename = "ok")]
> > > + Ok,
> > > + /// The fabric does not exist or doesn't distribute any routes
> > > + #[serde(rename = "not ok")]
> > > + NotOk,
> > > + }
> > > +
> > > + /// Status of a fabric.
> > > + ///
> > > + /// Check if there are any routes, if yes, then the status is ok, otherwise not ok.
> >
> > ^ Not sure how this describes the *struct*, though ;-)
>
> Oops, this slipped through, should have been somewhere else.
>
> > > + #[derive(Debug, Serialize)]
> > > + pub struct Status {
> > > + #[serde(rename = "type")]
> > > + ty: String,
> > > + status: FabricStatus,
> > > + protocol: Protocol,
> > > + sdn: FabricId,
> > > + sdn_type: String,
> > > + }
> > > +
> > > + /// Parsed routes for all protocols
> > > + ///
> > > + /// These are the routes parsed from the json output of:
> > > + /// `vtysh -c 'show ip route <protocol> json'`.
> > > + #[derive(Debug, Serialize)]
> > > + pub struct RoutesParsed {
> > > + /// All openfabric routes in FRR
> > > + pub openfabric: de::Routes,
> > > + /// All ospf routes in FRR
> > > + pub ospf: de::Routes,
> > > + }
> > > +
> > > + impl TryInto<HashMap<FabricId, Status>> for RoutesParsed {
> > > + type Error = anyhow::Error;
> > > +
> > > + fn try_into(self) -> Result<HashMap<FabricId, Status>, 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")?;
> >
> > ^ I'm really not a fan of doing file I/O in a TryInto implementation.
> > These are still supposed to be "simple"[1].
> >
> > Better make this a method.
> >
> > [1] https://doc.rust-lang.org/std/convert/trait.TryFrom.html
>
> Yup, I agree, changed all the TryInto impls to functions `get_routes`,
> `get_neighbors` and `get_status`.
>
> Also fixed all the other stuff below.
>
> Thanks for the review!
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [pve-devel] [PATCH proxmox-perl-rs v2 2/4] fabrics: add function to get status of fabric
2025-08-25 14:37 ` Wolfgang Bumiller
@ 2025-08-25 15:33 ` Gabriel Goller
2025-08-26 7:55 ` Wolfgang Bumiller
0 siblings, 1 reply; 25+ messages in thread
From: Gabriel Goller @ 2025-08-25 15:33 UTC (permalink / raw)
To: Wolfgang Bumiller; +Cc: pve-devel
On 25.08.2025 16:37, Wolfgang Bumiller wrote:
>On Mon, Aug 25, 2025 at 01:39:48PM +0200, Gabriel Goller wrote:
>> On 25.08.2025 10:11, Wolfgang Bumiller wrote:
>> > > diff --git a/pve-rs/src/bindings/sdn/fabrics.rs b/pve-rs/src/bindings/sdn/fabrics.rs
>> > > index 1dc8bf4320e6..3f70d421e582 100644
>> > > --- a/pve-rs/src/bindings/sdn/fabrics.rs
>> > > +++ b/pve-rs/src/bindings/sdn/fabrics.rs
>> > > @@ -9,8 +9,10 @@ pub mod pve_rs_sdn_fabrics {
>> > > use std::fmt::Write;
>> > > use std::net::IpAddr;
>> > > use std::ops::Deref;
>> > > + use std::process::Command;
>> > > use std::sync::Mutex;
>> > >
>> > > + use anyhow::Context;
>> > > use anyhow::Error;
>> > > use openssl::hash::{MessageDigest, hash};
>> > > use serde::{Deserialize, Serialize};
>> > > @@ -578,4 +580,196 @@ pub mod pve_rs_sdn_fabrics {
>> > >
>> > > Ok(interfaces)
>> > > }
>> > > +
>> > > + /// This module contains status-related structs that represent Routes and Neighbors for all
>> > > + /// protocols
>> > > + pub mod status {
>> >
>> > ^ This seems to be a helper module which does not contain any
>> > perlmod/perl specifics.
>> > I'd argue it's time to start a `crate::sdn` module outside of the
>> > `bindings` submodule for this.
>> >
>> > The `bindings` module should become rather more lean in the future and
>> > focus mostly on the perl/rust interaction.
>>
>> Umm do I understand you correctly that you want to have something like
>> this:
>>
>> src/
>> ├─ bindings/
>> │ ├─ sdn/
>> │ │ ├─ fabrics.rs
>> ├─ sdn/
>> │ ├─ status.rs
>>
>> ?
>
>Yes. The bindings should just be the perl interface and its point is to
>provide documentation via rustdoc, and the rustdocs should tell you how
>to use it *from perl*.
>
>The rest would be additional code we need to provide the perl interface
>for the external crates.
>
>> IMO we could move all the status stuff out to
>> crate::bindings::sdn::status. But I don't know about separating all the
>> types, conversion methods and actual perl methods -- I'd rather keep all
>> the perl-facing stuff in the same file.
>
>You don't need to separate everything. My point was that it does *not*
>contain perl *binding* specifics. As for being *perl* specific, I mean,
>the entire `pve-rs` crate *is* perl specific right now...
True :)
Should we maybe have a 'types' module in the middle, so that we have:
src/bindings/sdn/fabrics.rs
src/types/sdn/fabrics.rs
?
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [pve-devel] [PATCH proxmox-perl-rs v2 2/4] fabrics: add function to get status of fabric
2025-08-25 15:33 ` Gabriel Goller
@ 2025-08-26 7:55 ` Wolfgang Bumiller
2025-08-26 8:29 ` Gabriel Goller
0 siblings, 1 reply; 25+ messages in thread
From: Wolfgang Bumiller @ 2025-08-26 7:55 UTC (permalink / raw)
To: pve-devel
On Mon, Aug 25, 2025 at 05:33:49PM +0200, Gabriel Goller wrote:
> On 25.08.2025 16:37, Wolfgang Bumiller wrote:
> > On Mon, Aug 25, 2025 at 01:39:48PM +0200, Gabriel Goller wrote:
> > > On 25.08.2025 10:11, Wolfgang Bumiller wrote:
> > > > > diff --git a/pve-rs/src/bindings/sdn/fabrics.rs b/pve-rs/src/bindings/sdn/fabrics.rs
> > > > > index 1dc8bf4320e6..3f70d421e582 100644
> > > > > --- a/pve-rs/src/bindings/sdn/fabrics.rs
> > > > > +++ b/pve-rs/src/bindings/sdn/fabrics.rs
> > > > > @@ -9,8 +9,10 @@ pub mod pve_rs_sdn_fabrics {
> > > > > use std::fmt::Write;
> > > > > use std::net::IpAddr;
> > > > > use std::ops::Deref;
> > > > > + use std::process::Command;
> > > > > use std::sync::Mutex;
> > > > >
> > > > > + use anyhow::Context;
> > > > > use anyhow::Error;
> > > > > use openssl::hash::{MessageDigest, hash};
> > > > > use serde::{Deserialize, Serialize};
> > > > > @@ -578,4 +580,196 @@ pub mod pve_rs_sdn_fabrics {
> > > > >
> > > > > Ok(interfaces)
> > > > > }
> > > > > +
> > > > > + /// This module contains status-related structs that represent Routes and Neighbors for all
> > > > > + /// protocols
> > > > > + pub mod status {
> > > >
> > > > ^ This seems to be a helper module which does not contain any
> > > > perlmod/perl specifics.
> > > > I'd argue it's time to start a `crate::sdn` module outside of the
> > > > `bindings` submodule for this.
> > > >
> > > > The `bindings` module should become rather more lean in the future and
> > > > focus mostly on the perl/rust interaction.
> > >
> > > Umm do I understand you correctly that you want to have something like
> > > this:
> > >
> > > src/
> > > ├─ bindings/
> > > │ ├─ sdn/
> > > │ │ ├─ fabrics.rs
> > > ├─ sdn/
> > > │ ├─ status.rs
> > >
> > > ?
> >
> > Yes. The bindings should just be the perl interface and its point is to
> > provide documentation via rustdoc, and the rustdocs should tell you how
> > to use it *from perl*.
> >
> > The rest would be additional code we need to provide the perl interface
> > for the external crates.
> >
> > > IMO we could move all the status stuff out to
> > > crate::bindings::sdn::status. But I don't know about separating all the
> > > types, conversion methods and actual perl methods -- I'd rather keep all
> > > the perl-facing stuff in the same file.
> >
> > You don't need to separate everything. My point was that it does *not*
> > contain perl *binding* specifics. As for being *perl* specific, I mean,
> > the entire `pve-rs` crate *is* perl specific right now...
>
> True :)
> Should we maybe have a 'types' module in the middle, so that we have:
>
> src/bindings/sdn/fabrics.rs
> src/types/sdn/fabrics.rs
Not really a fan of this. I'd rather have the toplevel modules by
feature.
This *may* make sense if/when we start putting API types there and
serialize the schema out into perl, which is something we should
take into serious consideration soon-ish. It may be the complement or
even improve the situation with the pve-api-types crate. (While *at
first* it will definitely be a nuisance with where to find type
definitions and getting them updated... but in the long run? Anyway...
this is out of scope here :) )
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [pve-devel] [PATCH proxmox-perl-rs v2 2/4] fabrics: add function to get status of fabric
2025-08-26 7:55 ` Wolfgang Bumiller
@ 2025-08-26 8:29 ` Gabriel Goller
0 siblings, 0 replies; 25+ messages in thread
From: Gabriel Goller @ 2025-08-26 8:29 UTC (permalink / raw)
To: Wolfgang Bumiller; +Cc: pve-devel
On 26.08.2025 09:55, Wolfgang Bumiller wrote:
>On Mon, Aug 25, 2025 at 05:33:49PM +0200, Gabriel Goller wrote:
>> On 25.08.2025 16:37, Wolfgang Bumiller wrote:
>> > On Mon, Aug 25, 2025 at 01:39:48PM +0200, Gabriel Goller wrote:
>> > > On 25.08.2025 10:11, Wolfgang Bumiller wrote:
>> > > > > diff --git a/pve-rs/src/bindings/sdn/fabrics.rs b/pve-rs/src/bindings/sdn/fabrics.rs
>> > > > > index 1dc8bf4320e6..3f70d421e582 100644
>> > > > > --- a/pve-rs/src/bindings/sdn/fabrics.rs
>> > > > > +++ b/pve-rs/src/bindings/sdn/fabrics.rs
>> > > > > @@ -9,8 +9,10 @@ pub mod pve_rs_sdn_fabrics {
>> > > > > use std::fmt::Write;
>> > > > > use std::net::IpAddr;
>> > > > > use std::ops::Deref;
>> > > > > + use std::process::Command;
>> > > > > use std::sync::Mutex;
>> > > > >
>> > > > > + use anyhow::Context;
>> > > > > use anyhow::Error;
>> > > > > use openssl::hash::{MessageDigest, hash};
>> > > > > use serde::{Deserialize, Serialize};
>> > > > > @@ -578,4 +580,196 @@ pub mod pve_rs_sdn_fabrics {
>> > > > >
>> > > > > Ok(interfaces)
>> > > > > }
>> > > > > +
>> > > > > + /// This module contains status-related structs that represent Routes and Neighbors for all
>> > > > > + /// protocols
>> > > > > + pub mod status {
>> > > >
>> > > > ^ This seems to be a helper module which does not contain any
>> > > > perlmod/perl specifics.
>> > > > I'd argue it's time to start a `crate::sdn` module outside of the
>> > > > `bindings` submodule for this.
>> > > >
>> > > > The `bindings` module should become rather more lean in the future and
>> > > > focus mostly on the perl/rust interaction.
>> > >
>> > > Umm do I understand you correctly that you want to have something like
>> > > this:
>> > >
>> > > src/
>> > > ├─ bindings/
>> > > │ ├─ sdn/
>> > > │ │ ├─ fabrics.rs
>> > > ├─ sdn/
>> > > │ ├─ status.rs
>> > >
>> > > ?
>> >
>> > Yes. The bindings should just be the perl interface and its point is to
>> > provide documentation via rustdoc, and the rustdocs should tell you how
>> > to use it *from perl*.
>> >
>> > The rest would be additional code we need to provide the perl interface
>> > for the external crates.
>> >
>> > > IMO we could move all the status stuff out to
>> > > crate::bindings::sdn::status. But I don't know about separating all the
>> > > types, conversion methods and actual perl methods -- I'd rather keep all
>> > > the perl-facing stuff in the same file.
>> >
>> > You don't need to separate everything. My point was that it does *not*
>> > contain perl *binding* specifics. As for being *perl* specific, I mean,
>> > the entire `pve-rs` crate *is* perl specific right now...
>>
>> True :)
>> Should we maybe have a 'types' module in the middle, so that we have:
>>
>> src/bindings/sdn/fabrics.rs
>> src/types/sdn/fabrics.rs
>
>Not really a fan of this. I'd rather have the toplevel modules by
>feature.
ACK
>This *may* make sense if/when we start putting API types there and
>serialize the schema out into perl, which is something we should
>take into serious consideration soon-ish. It may be the complement or
>even improve the situation with the pve-api-types crate. (While *at
>first* it will definitely be a nuisance with where to find type
>definitions and getting them updated... but in the long run? Anyway...
>this is out of scope here :) )
Agree.
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [pve-devel] [PATCH manager/network/proxmox{-ve-rs, -perl-rs} v2 00/12] Add fabric status view
2025-08-22 9:00 [pve-devel] [PATCH manager/network/proxmox{-ve-rs, -perl-rs} v2 00/12] Add fabric status view Gabriel Goller
` (11 preceding siblings ...)
2025-08-22 9:00 ` [pve-devel] [PATCH pve-manager v2 3/3] permissions: differentiate between zone and fabric paths Gabriel Goller
@ 2025-08-26 9:52 ` Gabriel Goller
12 siblings, 0 replies; 25+ messages in thread
From: Gabriel Goller @ 2025-08-26 9:52 UTC (permalink / raw)
To: pve-devel; +Cc: Wolfgang Bumiller
Sent a new version:
https://lore.proxmox.com/pve-devel/20250826095000.180173-1-g.goller@proxmox.com/
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 25+ messages in thread
end of thread, other threads:[~2025-08-26 9:52 UTC | newest]
Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-08-22 9:00 [pve-devel] [PATCH manager/network/proxmox{-ve-rs, -perl-rs} v2 00/12] Add fabric status view Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-ve-rs v2 1/2] frr: make room for deserialization structs Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-ve-rs v2 2/2] frr: add deserialization types for openfabric and ospf Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-perl-rs v2 1/4] pve: fabrics: update proxmox-frr import path Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-perl-rs v2 2/4] fabrics: add function to get status of fabric Gabriel Goller
2025-08-25 8:11 ` Wolfgang Bumiller
2025-08-25 8:25 ` Wolfgang Bumiller
2025-08-25 11:39 ` Gabriel Goller
2025-08-25 14:37 ` Wolfgang Bumiller
2025-08-25 15:33 ` Gabriel Goller
2025-08-26 7:55 ` Wolfgang Bumiller
2025-08-26 8:29 ` Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-perl-rs v2 3/4] fabrics: add function to get all routes distributed by the fabrics Gabriel Goller
2025-08-25 8:22 ` Wolfgang Bumiller
2025-08-25 11:40 ` Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH proxmox-perl-rs v2 4/4] fabrics: add function to get all neighbors of the fabric Gabriel Goller
2025-08-25 8:28 ` Wolfgang Bumiller
2025-08-25 11:41 ` Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH pve-network v2 1/3] fabrics: add fabrics status to SDN::status function Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH pve-network v2 2/3] fabrics: add api endpoint to return fabrics routes Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH pve-network v2 3/3] fabrics: add api endpoint to return fabric neighbors Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH pve-manager v2 1/3] pvestatd: add fabrics status to pvestatd Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH pve-manager v2 2/3] fabrics: add resource view for fabrics Gabriel Goller
2025-08-22 9:00 ` [pve-devel] [PATCH pve-manager v2 3/3] permissions: differentiate between zone and fabric paths Gabriel Goller
2025-08-26 9:52 ` [pve-devel] [PATCH manager/network/proxmox{-ve-rs, -perl-rs} v2 00/12] Add fabric status view Gabriel Goller
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox