[pve-devel] [PATCH proxmox-ve-rs v4 11/22] config: sdn: fabrics: add node section types
Wolfgang Bumiller
w.bumiller at proxmox.com
Mon Jul 7 14:19:36 CEST 2025
On Wed, Jul 02, 2025 at 04:50:02PM +0200, Gabriel Goller wrote:
> From: Stefan Hanreich <s.hanreich at proxmox.com>
>
> NodeSection functions identically to the FabricSection type. It
> contains all the common properties that nodes from all protocols have.
> Protocol-specific properties can be defined via the type parameter of
> NodeSection. It also provides generic implementations for ApiType, so
> if the type parameter implements ApiType, then NodeSection<T> also
> implements ApiType.
>
> Together, FabricSection and NodeSection represent the two different
> types of entities in the fabric section configuration, fabrics and
> nodes.
>
> IP addresses are optional because this enables nodes to be part of a
> fabric without advertising an IP themselves. This enables nodes to
> import routes from the fabric without announcing a route to
> themselves. Also, since there can be either IPv4 or IPv6 (or both)
> set, they have to be optional anyway.
>
> The ID of a node is defined as the hostname of a node in the fabric,
> but since nodes can be part of multiple fabrics their section config
> entry can only be uniquely identified by a combination of the ID of
> the fabric they belong to and the ID of the node. For this reason, the
> ID of a node in the section config consists of the ID of the fabric as
> well as the ID of the node, separated by an underscore. We provide a
> helper struct for parsing the section ID into its two separate
> components, so we can easily parse the ID on deserializing the section
> config and easily serialize it back into its composite form when
> serializing.
>
> Co-authored-by: Gabriel Goller <g.goller at proxmox.com>
> Signed-off-by: Stefan Hanreich <s.hanreich at proxmox.com>
> ---
> .../src/sdn/fabric/section_config/mod.rs | 1 +
> .../src/sdn/fabric/section_config/node.rs | 169 ++++++++++++++++++
> 2 files changed, 170 insertions(+)
> create mode 100644 proxmox-ve-config/src/sdn/fabric/section_config/node.rs
>
> diff --git a/proxmox-ve-config/src/sdn/fabric/section_config/mod.rs b/proxmox-ve-config/src/sdn/fabric/section_config/mod.rs
> index 8106b6c2b156..0ca56958b3a8 100644
> --- a/proxmox-ve-config/src/sdn/fabric/section_config/mod.rs
> +++ b/proxmox-ve-config/src/sdn/fabric/section_config/mod.rs
> @@ -1 +1,2 @@
> pub mod fabric;
> +pub mod node;
> diff --git a/proxmox-ve-config/src/sdn/fabric/section_config/node.rs b/proxmox-ve-config/src/sdn/fabric/section_config/node.rs
> new file mode 100644
> index 000000000000..b1202a21e75b
> --- /dev/null
> +++ b/proxmox-ve-config/src/sdn/fabric/section_config/node.rs
> @@ -0,0 +1,169 @@
> +use const_format::concatcp;
> +use proxmox_schema::api_types::{IP_V4_SCHEMA, IP_V6_SCHEMA};
> +use serde::{Deserialize, Serialize};
> +use serde_with::{DeserializeFromStr, SerializeDisplay};
> +
> +use proxmox_network_types::ip_address::api_types::{Ipv4Addr, Ipv6Addr};
> +
> +use proxmox_schema::{
> + api, api_string_type, const_regex, AllOfSchema, ApiStringFormat, ApiType, ObjectSchema, Schema,
> + StringSchema, UpdaterType,
> +};
> +
> +use crate::sdn::fabric::section_config::{
> + fabric::{FabricId, FABRIC_ID_REGEX_STR},
> +};
> +
> +pub const NODE_ID_REGEX_STR: &str = r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]){0,61}(?:[a-zA-Z0-9]){0,1})";
> +
> +const_regex! {
> + pub NODE_ID_REGEX = concatcp!(r"^", NODE_ID_REGEX_STR, r"$");
> + pub NODE_SECTION_ID_REGEX = concatcp!(r"^", FABRIC_ID_REGEX_STR, r"_", NODE_ID_REGEX_STR, r"$");
> +}
> +
> +pub const NODE_ID_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&NODE_ID_REGEX);
> +pub const NODE_SECTION_ID_FORMAT: ApiStringFormat =
> + ApiStringFormat::Pattern(&NODE_SECTION_ID_REGEX);
> +
> +api_string_type! {
> + /// ID of a node in an SDN fabric.
> + ///
> + /// This corresponds to the hostname of the node.
> + #[api(format: &NODE_ID_FORMAT)]
> + #[derive(Debug, Deserialize, Serialize, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, UpdaterType)]
> + pub struct NodeId(String);
> +}
> +
> +/// ID of a node in the section config.
> +///
> +/// This corresponds to the ID of the fabric, that contains this node, as well as the hostname of
> +/// the node. They are joined by an underscore.
> +///
> +/// This struct is a helper for parsing the string into the two separate parts. It (de-)serializes
> +/// from and into a String.
> +#[derive(
> + Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, SerializeDisplay, DeserializeFromStr,
> +)]
> +pub struct NodeSectionId {
> + pub(crate) fabric_id: FabricId,
> + pub(crate) node_id: NodeId,
> +}
> +
> +impl ApiType for NodeSectionId {
> + const API_SCHEMA: Schema = StringSchema::new("ID of a SDN node in the section config")
> + .format(&NODE_SECTION_ID_FORMAT)
> + .schema();
> +}
> +
> +impl NodeSectionId {
> + /// Build a new [NodeSectionId] from the passed [FabricId] and [NodeId].
> + pub fn new(fabric_id: FabricId, node_id: NodeId) -> Self {
> + Self { fabric_id, node_id }
> + }
> +
> + /// Get the fabric part of the [NodeSectionId].
> + pub fn fabric_id(&self) -> &FabricId {
> + &self.fabric_id
> + }
> +
> + /// Get the node part of the [NodeSectionId].
> + pub fn node_id(&self) -> &NodeId {
> + &self.node_id
> + }
> +}
> +
> +impl std::str::FromStr for NodeSectionId {
> + type Err = anyhow::Error;
> +
> + fn from_str(value: &str) -> Result<Self, Self::Err> {
> + let (fabric_id, node_id) = value.split_once("_").unwrap();
> +
> + Ok(Self {
> + fabric_id: FabricId::from_string(fabric_id.to_string())?,
> + node_id: NodeId::from_string(node_id.to_string())?,
> + })
> + }
> +}
> +
> +impl std::fmt::Display for NodeSectionId {
> + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
> + write!(f, "{}_{}", self.fabric_id.as_ref(), self.node_id)
^ The .as_ref() could be dropped - or also added to node_id... but they
both implement `Display` by forwarding anyway.
> + }
> +}
> +
> +const NODE_SECTION_SCHEMA: Schema = ObjectSchema::new(
> + "Common properties for a node in an SDN fabric.",
> + &[
> + ("id", false, &NodeSectionId::API_SCHEMA),
> + ("ip", true, &IP_V4_SCHEMA),
> + ("ip6", true, &IP_V6_SCHEMA),
> + ],
> +)
> +.schema();
> +
> +/// A node section in an SDN fabric config.
> +///
> +/// This struct contains all the properties that are required for any node, regardless of
> +/// protocol. Properties that are specific to a protocol can be passed via the type parameter.
> +///
> +/// This is mainly used by the [Node] and [super::Section] enums to specify which types of nodes can exist,
> +/// without having to re-define common properties for every node. It also simplifies accessing
> +/// common properties by encapsulating the specific properties to [NodeSection<T>::properties].
> +#[derive(Debug, Clone, Serialize, Deserialize, Hash)]
> +pub struct NodeSection<T> {
> + pub(crate) id: NodeSectionId,
> +
> + /// IPv4 for this node in the fabric
> + #[serde(skip_serializing_if = "Option::is_none")]
> + pub(crate) ip: Option<Ipv4Addr>,
> +
> + /// IPv6 for this node in the fabric
> + #[serde(skip_serializing_if = "Option::is_none")]
> + pub(crate) ip6: Option<Ipv6Addr>,
> +
> + #[serde(flatten)]
> + pub(crate) properties: T,
> +}
> +
> +impl<T> NodeSection<T> {
> + /// Get the protocol-specific properties of the [NodeSection].
> + pub fn properties(&self) -> &T {
> + &self.properties
> + }
> +
> + /// Get a mutable reference to the protocol-specific properties of the [NodeSection].
> + pub fn properties_mut(&mut self) -> &mut T {
> + &mut self.properties
> + }
> +
> + /// Get the id of the [NodeSection].
> + pub fn id(&self) -> &NodeSectionId {
> + &self.id
> + }
> +
> + /// Get the IPv4 address (Router-ID) of the [NodeSection].
> + ///
> + /// Either the [NodeSection::ip] (IPv4) address or the [NodeSection::ip6] (IPv6) address *must*
> + /// be set. This is checked during the validation, so it's guaranteed. OpenFabric can also be
> + /// used dual-stack, so both IPv4 and IPv6 addresses can be set.
> + pub fn ip(&self) -> Option<std::net::Ipv4Addr> {
> + self.ip.as_deref().copied()
> + }
> +
> + /// Get the IPv6 address (Router-ID) of the [NodeSection].
> + ///
> + /// Either the [NodeSection::ip] (IPv4) address or the [NodeSection::ip6] (IPv6) address *must*
> + /// be set. This is checked during the validation, so it's guaranteed. OpenFabric can also be
> + /// used dual-stack, so both IPv4 and IPv6 addresses can be set.
> + pub fn ip6(&self) -> Option<std::net::Ipv6Addr> {
> + self.ip6.as_deref().copied()
> + }
> +}
> +
> +impl<T: ApiType> ApiType for NodeSection<T> {
> + const API_SCHEMA: Schema = AllOfSchema::new(
> + "Node in an SDN fabric.",
> + &[&NODE_SECTION_SCHEMA, &T::API_SCHEMA],
> + )
> + .schema();
> +}
> --
> 2.39.5
More information about the pve-devel
mailing list