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 C55851FF164 for <inbox@lore.proxmox.com>; Fri, 28 Mar 2025 18:15:28 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 0F3558816; Fri, 28 Mar 2025 18:14:05 +0100 (CET) From: Gabriel Goller <g.goller@proxmox.com> To: pve-devel@lists.proxmox.com Date: Fri, 28 Mar 2025 18:13:01 +0100 Message-Id: <20250328171340.885413-14-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 12/17] ve-config: add openfabric 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 openfabric configuration. It is used to parse from the section-config file (`/etc/pve/sdn/fabrics/openfabric.cfg`) and is also returned from the api. Signed-off-by: Gabriel Goller <g.goller@proxmox.com> --- proxmox-ve-config/Cargo.toml | 9 +- .../src/sdn/fabric/openfabric/mod.rs | 291 ++++++++++++++++++ 2 files changed, 297 insertions(+), 3 deletions(-) create mode 100644 proxmox-ve-config/src/sdn/fabric/openfabric/mod.rs diff --git a/proxmox-ve-config/Cargo.toml b/proxmox-ve-config/Cargo.toml index 4906d77550f3..3f7639efa153 100644 --- a/proxmox-ve-config/Cargo.toml +++ b/proxmox-ve-config/Cargo.toml @@ -10,14 +10,17 @@ exclude.workspace = true log = "0.4" anyhow = "1" nix = "0.26" -thiserror = "1.0.59" +thiserror = { workspace = true } -serde = { version = "1", features = [ "derive" ] } +serde = { workspace = true, features = [ "derive" ] } +serde_with = { workspace = true } serde_json = "1" serde_plain = "1" -serde_with = "3" +tracing = "0.1" proxmox-schema = "4" +proxmox-section-config = { workspace = true } +proxmox-serde = { version = "0.1.2" } proxmox-sys = "0.6.4" proxmox-sortable-macro = "0.1.3" proxmox-network-types = { version = "0.1", path = "../proxmox-network-types/" } diff --git a/proxmox-ve-config/src/sdn/fabric/openfabric/mod.rs b/proxmox-ve-config/src/sdn/fabric/openfabric/mod.rs new file mode 100644 index 000000000000..ae7c7eb5ac4f --- /dev/null +++ b/proxmox-ve-config/src/sdn/fabric/openfabric/mod.rs @@ -0,0 +1,291 @@ +#[cfg(feature = "frr")] +pub mod frr; +pub mod validation; + +use proxmox_network_types::{ + address::{Cidr, Ipv4Cidr, Ipv6Cidr}, + hostname::Hostname, + openfabric::{CsnpInterval, HelloInterval, HelloMultiplier}, +}; +use proxmox_schema::property_string::PropertyString; +use proxmox_sortable_macro::sortable; +use serde_with::{DeserializeFromStr, SerializeDisplay}; +use std::{fmt::Display, net::IpAddr, str::FromStr, sync::OnceLock}; + +use proxmox_schema::{ + ApiStringFormat, ApiType, ArraySchema, BooleanSchema, IntegerSchema, ObjectSchema, Schema, + StringSchema, +}; +use proxmox_section_config::{typed::ApiSectionDataEntry, SectionConfig, SectionConfigPlugin}; +use proxmox_serde::string_as_bool; +use serde::{Deserialize, Serialize}; + +#[sortable] +const FABRIC_SCHEMA: ObjectSchema = ObjectSchema::new( + "fabric schema", + &sorted!([ + ("fabric_id", false, &StringSchema::new("FabricId").schema()), + ( + "hello_interval", + true, + &IntegerSchema::new("OpenFabric hello_interval in seconds") + .minimum(1) + .maximum(600) + .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(), + ), + ( + "ip", + true, + &StringSchema::new("Interface IPv4 address").schema() + ), + ( + "ipv6", + true, + &StringSchema::new("Interface IPv6 address").schema() + ), + ( + "passive", + true, + &BooleanSchema::new("OpenFabric passive mode for this interface").schema(), + ), + ( + "hello_interval", + true, + &IntegerSchema::new("OpenFabric Hello interval in seconds") + .minimum(1) + .maximum(600) + .schema(), + ), + ( + "csnp_interval", + true, + &IntegerSchema::new("OpenFabric csnp interval in seconds") + .minimum(1) + .maximum(600) + .schema() + ), + ( + "hello_multiplier", + true, + &IntegerSchema::new("OpenFabric multiplier for Hello holding time") + .minimum(2) + .maximum(100) + .schema() + ), + ]), +) +.schema(); + +#[sortable] +const NODE_SCHEMA: ObjectSchema = ObjectSchema::new( + "node schema", + &sorted!([ + ( + "node_id", + false, + &StringSchema::new("NodeId containing the fabric_id and hostname").schema(), + ), + ( + "interface", + false, + &ArraySchema::new( + "OpenFabric name", + &StringSchema::new("OpenFabric Interface") + .format(&ApiStringFormat::PropertyString(&INTERFACE_SCHEMA)) + .schema(), + ) + .schema(), + ), + ( + "router_id", + false, + &StringSchema::new("OpenFabric 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, Hash)] +pub struct FabricSection { + pub fabric_id: FabricId, + #[serde(skip_serializing_if = "Option::is_none")] + pub hello_interval: Option<HelloInterval>, + pub loopback_prefix: Cidr, + pub ty: String, +} + +#[derive( + SerializeDisplay, DeserializeFromStr, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, +)] +pub struct FabricId(String); + +impl FabricId { + pub fn new(id: impl Into<String>) -> Result<Self, anyhow::Error> { + let value = id.into(); + if value.len() <= 8 { + Ok(Self(value)) + }else { + anyhow::bail!("FabricId has to be shorter than 8 characters"); + } + } +} + +impl AsRef<str> for FabricId { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl FromStr for FabricId { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + Self::new(s) + } +} + +impl From<String> for FabricId { + fn from(value: String) -> Self { + FabricId(value) + } +} + +impl Display for FabricId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct NodeSection { + pub router_id: IpAddr, + pub interface: Vec<PropertyString<InterfaceProperties>>, + pub node_id: NodeId, + pub ty: String, +} + +#[derive(SerializeDisplay, DeserializeFromStr, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct NodeId { + pub fabric_id: FabricId, + pub node: Hostname, +} + +impl Display for NodeId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}_{}", self.fabric_id, self.node) + } +} + +impl NodeId { + pub fn new(fabric_id: FabricId, node: Hostname) -> NodeId { + NodeId { fabric_id, node } + } +} + +impl FromStr for NodeId { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + if let Some((fabric_id, hostname)) = s.split_once('_') { + Ok(NodeId { + fabric_id: fabric_id.to_owned().into(), + node: hostname.to_owned().into(), + }) + } else { + anyhow::bail!("nothing works"); + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct InterfaceProperties { + pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default, with = "string_as_bool")] + pub passive: Option<bool>, + #[serde(skip_serializing_if = "Option::is_none")] + pub hello_interval: Option<HelloInterval>, + #[serde(skip_serializing_if = "Option::is_none")] + pub csnp_interval: Option<CsnpInterval>, + #[serde(skip_serializing_if = "Option::is_none")] + pub hello_multiplier: Option<HelloMultiplier>, + #[serde(skip_serializing_if = "Option::is_none")] + pub ip: Option<Ipv4Cidr>, + #[serde(skip_serializing_if = "Option::is_none")] + pub ipv6: Option<Ipv6Cidr>, +} + +impl InterfaceProperties { + pub fn passive(&self) -> Option<bool> { + self.passive + } +} + +impl ApiType for InterfaceProperties { + const API_SCHEMA: Schema = INTERFACE_SCHEMA; +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum OpenFabricSectionConfig { + Fabric(FabricSection), + Node(NodeSection), +} + +impl ApiSectionDataEntry for OpenFabricSectionConfig { + 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("fabric_id".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