[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