On Wed, Jul 02, 2025 at 04:50:02PM +0200, Gabriel Goller wrote: > From: Stefan Hanreich <s.hanre...@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.gol...@proxmox.com> > Signed-off-by: Stefan Hanreich <s.hanre...@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 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel