From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: <pve-devel-bounces@lists.proxmox.com> Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 3CB091FF164 for <inbox@lore.proxmox.com>; Fri, 28 Mar 2025 18:17:35 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 8C64A950F; Fri, 28 Mar 2025 18:14:26 +0100 (CET) From: Gabriel Goller <g.goller@proxmox.com> To: pve-devel@lists.proxmox.com Date: Fri, 28 Mar 2025 18:13:02 +0100 Message-Id: <20250328171340.885413-15-g.goller@proxmox.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250328171340.885413-1-g.goller@proxmox.com> References: <20250328171340.885413-1-g.goller@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.025 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pve-devel] [PATCH proxmox-ve-rs 13/17] ve-config: add ospf section-config X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion <pve-devel.lists.proxmox.com> List-Unsubscribe: <https://lists.proxmox.com/cgi-bin/mailman/options/pve-devel>, <mailto:pve-devel-request@lists.proxmox.com?subject=unsubscribe> List-Archive: <http://lists.proxmox.com/pipermail/pve-devel/> List-Post: <mailto:pve-devel@lists.proxmox.com> List-Help: <mailto:pve-devel-request@lists.proxmox.com?subject=help> List-Subscribe: <https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel>, <mailto:pve-devel-request@lists.proxmox.com?subject=subscribe> Reply-To: Proxmox VE development discussion <pve-devel@lists.proxmox.com> Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" <pve-devel-bounces@lists.proxmox.com> 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@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 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel