[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