[pve-devel] [PATCH proxmox-ve-rs 12/17] ve-config: add openfabric section-config

Gabriel Goller g.goller at proxmox.com
Fri Mar 28 18:13:01 CET 2025


This is the main openfabric configuration. It is used to parse from the
section-config file (`/etc/pve/sdn/fabrics/openfabric.cfg`) and is also
returned from the api.

Signed-off-by: Gabriel Goller <g.goller at proxmox.com>
---
 proxmox-ve-config/Cargo.toml                  |   9 +-
 .../src/sdn/fabric/openfabric/mod.rs          | 291 ++++++++++++++++++
 2 files changed, 297 insertions(+), 3 deletions(-)
 create mode 100644 proxmox-ve-config/src/sdn/fabric/openfabric/mod.rs

diff --git a/proxmox-ve-config/Cargo.toml b/proxmox-ve-config/Cargo.toml
index 4906d77550f3..3f7639efa153 100644
--- a/proxmox-ve-config/Cargo.toml
+++ b/proxmox-ve-config/Cargo.toml
@@ -10,14 +10,17 @@ exclude.workspace = true
 log = "0.4"
 anyhow = "1"
 nix = "0.26"
-thiserror = "1.0.59"
+thiserror = { workspace = true }
 
-serde = { version = "1", features = [ "derive" ] }
+serde = { workspace = true, features = [ "derive" ] }
+serde_with = { workspace = true }
 serde_json = "1"
 serde_plain = "1"
-serde_with = "3"
+tracing = "0.1"
 
 proxmox-schema = "4"
+proxmox-section-config = { workspace = true }
+proxmox-serde = { version = "0.1.2" }
 proxmox-sys = "0.6.4"
 proxmox-sortable-macro = "0.1.3"
 proxmox-network-types = { version = "0.1", path = "../proxmox-network-types/" }
diff --git a/proxmox-ve-config/src/sdn/fabric/openfabric/mod.rs b/proxmox-ve-config/src/sdn/fabric/openfabric/mod.rs
new file mode 100644
index 000000000000..ae7c7eb5ac4f
--- /dev/null
+++ b/proxmox-ve-config/src/sdn/fabric/openfabric/mod.rs
@@ -0,0 +1,291 @@
+#[cfg(feature = "frr")]
+pub mod frr;
+pub mod validation;
+
+use proxmox_network_types::{
+    address::{Cidr, Ipv4Cidr, Ipv6Cidr},
+    hostname::Hostname,
+    openfabric::{CsnpInterval, HelloInterval, HelloMultiplier},
+};
+use proxmox_schema::property_string::PropertyString;
+use proxmox_sortable_macro::sortable;
+use serde_with::{DeserializeFromStr, SerializeDisplay};
+use std::{fmt::Display, net::IpAddr, str::FromStr, sync::OnceLock};
+
+use proxmox_schema::{
+    ApiStringFormat, ApiType, ArraySchema, BooleanSchema, IntegerSchema, ObjectSchema, Schema,
+    StringSchema,
+};
+use proxmox_section_config::{typed::ApiSectionDataEntry, SectionConfig, SectionConfigPlugin};
+use proxmox_serde::string_as_bool;
+use serde::{Deserialize, Serialize};
+
+#[sortable]
+const FABRIC_SCHEMA: ObjectSchema = ObjectSchema::new(
+    "fabric schema",
+    &sorted!([
+        ("fabric_id", false, &StringSchema::new("FabricId").schema()),
+        (
+            "hello_interval",
+            true,
+            &IntegerSchema::new("OpenFabric hello_interval in seconds")
+                .minimum(1)
+                .maximum(600)
+                .schema(),
+        ),
+        (
+            "loopback_prefix",
+            false,
+            &StringSchema::new("Loopback IP prefix").min_length(1).schema()
+        ),
+    ]),
+);
+
+#[sortable]
+const INTERFACE_SCHEMA: Schema = ObjectSchema::new(
+    "interface",
+    &sorted!([
+        (
+            "name",
+            false,
+            &StringSchema::new("Interface name")
+                .min_length(1)
+                .max_length(15)
+                .schema(),
+        ),
+        (
+            "ip",
+            true,
+            &StringSchema::new("Interface IPv4 address").schema()
+        ),
+        (
+            "ipv6",
+            true,
+            &StringSchema::new("Interface IPv6 address").schema()
+        ),
+        (
+            "passive",
+            true,
+            &BooleanSchema::new("OpenFabric passive mode for this interface").schema(),
+        ),
+        (
+            "hello_interval",
+            true,
+            &IntegerSchema::new("OpenFabric Hello interval in seconds")
+                .minimum(1)
+                .maximum(600)
+                .schema(),
+        ),
+        (
+            "csnp_interval",
+            true,
+            &IntegerSchema::new("OpenFabric csnp interval in seconds")
+                .minimum(1)
+                .maximum(600)
+                .schema()
+        ),
+        (
+            "hello_multiplier",
+            true,
+            &IntegerSchema::new("OpenFabric multiplier for Hello holding time")
+                .minimum(2)
+                .maximum(100)
+                .schema()
+        ),
+    ]),
+)
+.schema();
+
+#[sortable]
+const NODE_SCHEMA: ObjectSchema = ObjectSchema::new(
+    "node schema",
+    &sorted!([
+        (
+            "node_id",
+            false,
+            &StringSchema::new("NodeId containing the fabric_id and hostname").schema(),
+        ),
+        (
+            "interface",
+            false,
+            &ArraySchema::new(
+                "OpenFabric name",
+                &StringSchema::new("OpenFabric Interface")
+                    .format(&ApiStringFormat::PropertyString(&INTERFACE_SCHEMA))
+                    .schema(),
+            )
+            .schema(),
+        ),
+        (
+            "router_id",
+            false,
+            &StringSchema::new("OpenFabric router-id")
+                .min_length(3)
+                .schema(),
+        ),
+    ]),
+);
+
+const ID_SCHEMA: Schema = StringSchema::new("id").min_length(2).schema();
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct FabricSection {
+    pub fabric_id: FabricId,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub hello_interval: Option<HelloInterval>,
+    pub loopback_prefix: Cidr,
+    pub ty: String,
+}
+
+#[derive(
+    SerializeDisplay, DeserializeFromStr, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
+)]
+pub struct FabricId(String);
+
+impl FabricId {
+    pub fn new(id: impl Into<String>) -> Result<Self, anyhow::Error> {
+        let value = id.into();
+        if value.len() <= 8 {
+            Ok(Self(value))
+        }else {
+            anyhow::bail!("FabricId has to be shorter than 8 characters");
+        }
+    }
+}
+
+impl AsRef<str> for FabricId {
+    fn as_ref(&self) -> &str {
+        &self.0
+    }
+}
+
+impl FromStr for FabricId {
+    type Err = anyhow::Error;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        Self::new(s)
+    }
+}
+
+impl From<String> for FabricId {
+    fn from(value: String) -> Self {
+        FabricId(value)
+    }
+}
+
+impl Display for FabricId {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct NodeSection {
+    pub router_id: IpAddr,
+    pub interface: Vec<PropertyString<InterfaceProperties>>,
+    pub node_id: NodeId,
+    pub ty: String,
+}
+
+#[derive(SerializeDisplay, DeserializeFromStr, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct NodeId {
+    pub fabric_id: FabricId,
+    pub node: Hostname,
+}
+
+impl Display for NodeId {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}_{}", self.fabric_id, self.node)
+    }
+}
+
+impl NodeId {
+    pub fn new(fabric_id: FabricId, node: Hostname) -> NodeId {
+        NodeId { fabric_id, node }
+    }
+}
+
+impl FromStr for NodeId {
+    type Err = anyhow::Error;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        if let Some((fabric_id, hostname)) = s.split_once('_') {
+            Ok(NodeId {
+                fabric_id: fabric_id.to_owned().into(),
+                node: hostname.to_owned().into(),
+            })
+        } else {
+            anyhow::bail!("nothing works");
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct InterfaceProperties {
+    pub name: String,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(default, with = "string_as_bool")]
+    pub passive: Option<bool>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub hello_interval: Option<HelloInterval>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub csnp_interval: Option<CsnpInterval>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub hello_multiplier: Option<HelloMultiplier>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub ip: Option<Ipv4Cidr>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub ipv6: Option<Ipv6Cidr>,
+}
+
+impl InterfaceProperties {
+    pub fn passive(&self) -> Option<bool> {
+        self.passive
+    }
+}
+
+impl ApiType for InterfaceProperties {
+    const API_SCHEMA: Schema = INTERFACE_SCHEMA;
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+#[serde(untagged)]
+pub enum OpenFabricSectionConfig {
+    Fabric(FabricSection),
+    Node(NodeSection),
+}
+
+impl ApiSectionDataEntry for OpenFabricSectionConfig {
+    const INTERNALLY_TAGGED: Option<&'static str> = Some("ty");
+
+    fn section_config() -> &'static SectionConfig {
+        static SC: OnceLock<SectionConfig> = OnceLock::new();
+
+        SC.get_or_init(|| {
+            let mut config = SectionConfig::new(&ID_SCHEMA);
+
+            let fabric_plugin = SectionConfigPlugin::new(
+                "fabric".to_string(),
+                Some("fabric_id".to_string()),
+                &FABRIC_SCHEMA,
+            );
+            config.register_plugin(fabric_plugin);
+
+            let node_plugin = SectionConfigPlugin::new(
+                "node".to_string(),
+                Some("node_id".to_string()),
+                &NODE_SCHEMA,
+            );
+            config.register_plugin(node_plugin);
+
+            config
+        })
+    }
+
+    fn section_type(&self) -> &'static str {
+        match self {
+            Self::Node(_) => "node",
+            Self::Fabric(_) => "fabric",
+        }
+    }
+}
-- 
2.39.5





More information about the pve-devel mailing list