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 [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 8BACE1FF15C for <inbox@lore.proxmox.com>; Fri, 4 Apr 2025 18:32:41 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id B2905139; Fri, 4 Apr 2025 18:29:56 +0200 (CEST) From: Gabriel Goller <g.goller@proxmox.com> To: pve-devel@lists.proxmox.com Date: Fri, 4 Apr 2025 18:28:23 +0200 Message-Id: <20250404162908.563060-13-g.goller@proxmox.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250404162908.563060-1-g.goller@proxmox.com> References: <20250404162908.563060-1-g.goller@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.073 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 PROLO_LEO1 0.1 Meta Catches all Leo drug variations so far 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 v2 11/15] 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 | 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 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel