[pve-devel] [PATCH proxmox-ve-rs 13/17] ve-config: add ospf section-config

Gabriel Goller g.goller at proxmox.com
Fri Mar 28 18:13:02 CET 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 | 245 +++++++++++++++++++
 1 file changed, 245 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..59691f0e84f0
--- /dev/null
+++ b/proxmox-ve-config/src/sdn/fabric/ospf/mod.rs
@@ -0,0 +1,245 @@
+#[cfg(feature = "frr")]
+pub mod frr;
+pub mod validation;
+
+use proxmox_network_types::address::Ipv4Cidr;
+use proxmox_network_types::hostname::Hostname;
+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;
+
+#[sortable]
+const FABRIC_SCHEMA: ObjectSchema = ObjectSchema::new(
+    "fabric schema",
+    &sorted!([
+        (
+            "area",
+            false,
+            &StringSchema::new("Area identifier").min_length(1).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!([
+        (
+            "node_id",
+            false,
+            &StringSchema::new("NodeId which contains area and node").schema()
+        ),
+        (
+            "interface",
+            false,
+            &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 node_id: NodeId,
+    pub router_id: Ipv4Addr,
+    pub interface: Vec<PropertyString<InterfaceProperties>>,
+    pub ty: String,
+}
+
+#[derive(SerializeDisplay, DeserializeFromStr, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct NodeId {
+    pub area: Area,
+    pub node: Hostname,
+}
+
+impl Display for NodeId {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}_{}", self.area, self.node)
+    }
+}
+
+impl NodeId {
+    pub fn new(area: Area, node: Hostname) -> NodeId {
+        NodeId { area, node }
+    }
+}
+
+impl FromStr for NodeId {
+    type Err = anyhow::Error;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        if let Some((areaa, hostname)) = s.split_once('_') {
+            Ok(NodeId {
+                area: areaa.parse()?,
+                node: hostname.to_owned().into(),
+            })
+        } else {
+            anyhow::bail!("nothing works");
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct FabricSection {
+    pub area: Area,
+    pub loopback_prefix: Ipv4Cidr,
+    pub ty: String,
+}
+
+#[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::<i32>().is_ok() || area.parse::<Ipv4Addr>().is_ok()) && area.len() <= 8 {
+            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 {
+    #[serde(rename = "fabric")]
+    Fabric(FabricSection),
+    #[serde(rename = "node")]
+    Node(NodeSection),
+}
+
+impl ApiSectionDataEntry for OspfSectionConfig {
+    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("area".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