[pve-devel] [PATCH proxmox-ve-rs v2 11/15] ve-config: add ospf section-config

Gabriel Goller g.goller at proxmox.com
Fri Apr 4 18:28:23 CEST 2025


This is the main configuration for OSPF. It is used to parse the section
config file and is returned from the api.

Signed-off-by: Gabriel Goller <g.goller at proxmox.com>
---
 proxmox-ve-config/src/sdn/fabric/ospf/mod.rs | 219 +++++++++++++++++++
 1 file changed, 219 insertions(+)
 create mode 100644 proxmox-ve-config/src/sdn/fabric/ospf/mod.rs

diff --git a/proxmox-ve-config/src/sdn/fabric/ospf/mod.rs b/proxmox-ve-config/src/sdn/fabric/ospf/mod.rs
new file mode 100644
index 000000000000..ece251645ee8
--- /dev/null
+++ b/proxmox-ve-config/src/sdn/fabric/ospf/mod.rs
@@ -0,0 +1,219 @@
+#[cfg(feature = "frr")]
+pub mod frr;
+
+use proxmox_network_types::debian::Hostname;
+use proxmox_network_types::ip_address::Ipv4Cidr;
+use proxmox_schema::property_string::PropertyString;
+
+use proxmox_schema::ObjectSchema;
+use proxmox_schema::{ApiStringFormat, ApiType, ArraySchema, BooleanSchema, Schema, StringSchema};
+use proxmox_section_config::{typed::ApiSectionDataEntry, SectionConfig, SectionConfigPlugin};
+use proxmox_sortable_macro::sortable;
+use serde::{Deserialize, Serialize};
+use serde_with::{DeserializeFromStr, SerializeDisplay};
+use std::fmt::Display;
+use std::net::Ipv4Addr;
+use std::str::FromStr;
+use std::sync::OnceLock;
+use thiserror::Error;
+
+use crate::sdn::fabric::{FabricId, NodeId, SectionType};
+
+#[sortable]
+const FABRIC_SCHEMA: ObjectSchema = ObjectSchema::new(
+    "fabric schema",
+    &sorted!([
+        (
+            "area",
+            false,
+            &StringSchema::new("Area identifier").min_length(1).schema()
+        ),
+        ("fabric_id", false, &StringSchema::new("Fabric ID").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(),
+        ),
+        (
+            "passive",
+            true,
+            &BooleanSchema::new("passive interface").schema(),
+        ),
+        (
+            "unnumbered",
+            true,
+            &BooleanSchema::new("unnumbered interface").schema(),
+        ),
+        (
+            "ip",
+            true,
+            &StringSchema::new("Interface IPv4 address").schema()
+        ),
+    ]),
+)
+.schema();
+
+#[sortable]
+const NODE_SCHEMA: ObjectSchema = ObjectSchema::new(
+    "node schema",
+    &sorted!([
+        (
+            "id",
+            false,
+            &StringSchema::new("NodeId which contains fabric id and node hostname").schema()
+        ),
+        ("fabric_id", false, &StringSchema::new("Fabric ID").schema()),
+        ("node_id", false, &StringSchema::new("Node ID").schema()),
+        (
+            "interfaces",
+            true,
+            &ArraySchema::new(
+                "OSPF name",
+                &StringSchema::new("OSPF Interface")
+                    .format(&ApiStringFormat::PropertyString(&INTERFACE_SCHEMA))
+                    .schema(),
+            )
+            .schema(),
+        ),
+        (
+            "router_id",
+            false,
+            &StringSchema::new("OSPF 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)]
+pub struct InterfaceProperties {
+    pub name: String,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub passive: Option<bool>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub unnumbered: Option<bool>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub ip: Option<Ipv4Cidr>,
+}
+
+impl ApiType for InterfaceProperties {
+    const API_SCHEMA: Schema = INTERFACE_SCHEMA;
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct NodeSection {
+    pub id: NodeId,
+    pub fabric_id: FabricId,
+    pub node_id: Hostname,
+    #[serde(rename = "type")]
+    pub ty: SectionType,
+    pub router_id: Ipv4Addr,
+    #[serde(default)]
+    pub interfaces: Vec<PropertyString<InterfaceProperties>>,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct FabricSection {
+    pub fabric_id: FabricId,
+    #[serde(rename = "type")]
+    pub ty: SectionType,
+    pub area: Area,
+    pub loopback_prefix: Ipv4Cidr,
+}
+
+#[derive(Error, Debug)]
+pub enum AreaParsingError {
+    #[error("Invalid area identifier. Area must be a number or a ipv4 address.")]
+    InvalidArea,
+}
+
+#[derive(
+    Debug, DeserializeFromStr, SerializeDisplay, Hash, PartialEq, Eq, PartialOrd, Ord, Clone,
+)]
+pub struct Area(String);
+
+impl Area {
+    pub fn new(area: String) -> Result<Area, AreaParsingError> {
+        if area.parse::<u32>().is_ok() || area.parse::<Ipv4Addr>().is_ok() {
+            Ok(Self(area))
+        } else {
+            Err(AreaParsingError::InvalidArea)
+        }
+    }
+}
+
+impl FromStr for Area {
+    type Err = AreaParsingError;
+
+    fn from_str(value: &str) -> Result<Self, Self::Err> {
+        Area::new(value.to_owned())
+    }
+}
+
+impl AsRef<str> for Area {
+    fn as_ref(&self) -> &str {
+        &self.0
+    }
+}
+
+impl Display for Area {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+#[serde(untagged)]
+pub enum OspfSectionConfig {
+    Fabric(FabricSection),
+    Node(NodeSection),
+}
+
+impl ApiSectionDataEntry for OspfSectionConfig {
+    const INTERNALLY_TAGGED: Option<&'static str> = Some("type");
+
+    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("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