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 DEBB31FF164
	for <inbox@lore.proxmox.com>; Fri, 28 Mar 2025 18:15:03 +0100 (CET)
Received: from firstgate.proxmox.com (localhost [127.0.0.1])
	by firstgate.proxmox.com (Proxmox) with ESMTP id 4D46D856D;
	Fri, 28 Mar 2025 18:14:01 +0100 (CET)
From: Gabriel Goller <g.goller@proxmox.com>
To: pve-devel@lists.proxmox.com
Date: Fri, 28 Mar 2025 18:13:04 +0100
Message-Id: <20250328171340.885413-17-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 15/17] ve-config: add validation
 for 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>

Our section-config is nested 3 times (fabric -> node -> interfaces), but
as only one indentation level (two with propertyStrings) are possible in
section-config configuration files, we need to add some validation to
ensure that the config is valid. In the future, more stuff to be
validated can be added here, but currently we check:

 * if the router-id is unique
 * if the node refers to a existing fabric
 * if the router-ids are in the specified loopback prefix

Our Section-Config is structured like this:

fabric: test
    fabric-option: this

node: test_pve0
    node-option: that

The key to the node section is called the `NodeId` and consist of two
parts: `test`, which is the fabric, and `pve0`, which is the nodename.
The validation checks if the `test` fabric exists.

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
Co-authored-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 proxmox-ve-config/src/sdn/fabric/mod.rs       | 44 +++++++++++++++
 .../src/sdn/fabric/openfabric/validation.rs   | 56 +++++++++++++++++++
 .../src/sdn/fabric/ospf/validation.rs         | 53 ++++++++++++++++++
 proxmox-ve-config/src/sdn/mod.rs              |  1 +
 4 files changed, 154 insertions(+)
 create mode 100644 proxmox-ve-config/src/sdn/fabric/mod.rs
 create mode 100644 proxmox-ve-config/src/sdn/fabric/openfabric/validation.rs
 create mode 100644 proxmox-ve-config/src/sdn/fabric/ospf/validation.rs

diff --git a/proxmox-ve-config/src/sdn/fabric/mod.rs b/proxmox-ve-config/src/sdn/fabric/mod.rs
new file mode 100644
index 000000000000..949486a86355
--- /dev/null
+++ b/proxmox-ve-config/src/sdn/fabric/mod.rs
@@ -0,0 +1,44 @@
+pub mod openfabric;
+pub mod ospf;
+
+use openfabric::OpenFabricSectionConfig;
+use ospf::OspfSectionConfig;
+use proxmox_section_config::typed::ApiSectionDataEntry;
+use proxmox_section_config::typed::SectionConfigData;
+
+use std::ops::Deref;
+
+use serde::de::DeserializeOwned;
+
+#[derive(Debug, Clone)]
+pub struct Valid<T>(SectionConfigData<T>);
+
+impl<T> Deref for Valid<T> {
+    type Target = SectionConfigData<T>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+pub trait Validate<T> {
+    fn validate(data: SectionConfigData<T>) -> Result<Valid<T>, anyhow::Error>;
+    fn validate_as_ref(data: &SectionConfigData<T>) -> Result<(), anyhow::Error>;
+}
+
+impl<T> Valid<T> {
+    pub fn into_inner(self) -> SectionConfigData<T> {
+        self.0
+    }
+}
+
+impl<T> Valid<T>
+where
+    T: ApiSectionDataEntry + DeserializeOwned + Validate<T>,
+{
+    pub fn parse_section_config(filename: &str, data: &str) -> Result<Valid<T>, anyhow::Error> {
+        let config = T::parse_section_config(filename, data)?;
+        T::validate(config)
+    }
+}
+
diff --git a/proxmox-ve-config/src/sdn/fabric/openfabric/validation.rs b/proxmox-ve-config/src/sdn/fabric/openfabric/validation.rs
new file mode 100644
index 000000000000..dfb9ee94596a
--- /dev/null
+++ b/proxmox-ve-config/src/sdn/fabric/openfabric/validation.rs
@@ -0,0 +1,56 @@
+use anyhow::{anyhow, bail};
+use std::collections::{HashMap, HashSet};
+
+use proxmox_section_config::typed::SectionConfigData;
+
+use crate::sdn::fabric::{Valid, Validate};
+
+use super::OpenFabricSectionConfig;
+
+impl Validate<OpenFabricSectionConfig> for OpenFabricSectionConfig {
+    /// This function will validate the SectionConfigData<T> and return a Valid<SectionConfigData<T>>
+    /// The validation checks if the every node is part of an existing fabric. This is necessary as
+    /// with the current SectionConfigData format, we don't get this guarantee.
+    fn validate(
+        data: SectionConfigData<OpenFabricSectionConfig>,
+    ) -> Result<Valid<OpenFabricSectionConfig>, anyhow::Error> {
+        Self::validate_as_ref(&data)?;
+        Ok(Valid(data))
+    }
+
+    fn validate_as_ref(
+        data: &SectionConfigData<OpenFabricSectionConfig>,
+    ) -> Result<(), anyhow::Error> {
+        let mut fabrics = HashMap::new();
+        let mut nodes = Vec::new();
+
+        for (_, section) in data {
+            match section {
+                OpenFabricSectionConfig::Node(node) => {
+                    nodes.push(node);
+                }
+                OpenFabricSectionConfig::Fabric(fabric) => {
+                    fabrics.insert(&fabric.fabric_id, fabric);
+                }
+            }
+        }
+
+        let mut router_ids = HashSet::new();
+
+        for node in nodes {
+            let fabric = fabrics
+                .get(&node.node_id.fabric_id)
+                .ok_or_else(|| anyhow!("verification error - missing fabric configuration"))?;
+
+            if !router_ids.insert(node.router_id) {
+                bail!("verification error - duplicate router_id");
+            }
+
+            if !fabric.loopback_prefix.contains_address(&node.router_id) {
+                bail!("Loopback IP of node is not contained in Loopback IP prefix");
+            }
+        }
+
+        Ok(())
+    }
+}
diff --git a/proxmox-ve-config/src/sdn/fabric/ospf/validation.rs b/proxmox-ve-config/src/sdn/fabric/ospf/validation.rs
new file mode 100644
index 000000000000..e931ba279afa
--- /dev/null
+++ b/proxmox-ve-config/src/sdn/fabric/ospf/validation.rs
@@ -0,0 +1,53 @@
+use anyhow::{anyhow, bail};
+use std::collections::{HashMap, HashSet};
+
+use proxmox_section_config::typed::SectionConfigData;
+
+use crate::sdn::fabric::{Valid, Validate};
+
+use super::OspfSectionConfig;
+
+impl Validate<OspfSectionConfig> for OspfSectionConfig {
+    /// This function will validate the SectionConfigData<T> and return a Valid<SectionConfigData<T>>
+    /// The validation checks if the every node is part of an existing fabric. This is necessary as
+    /// with the current SectionConfigData format, we don't get this guarantee.
+    fn validate(
+        data: SectionConfigData<OspfSectionConfig>,
+    ) -> Result<Valid<OspfSectionConfig>, anyhow::Error> {
+        Self::validate_as_ref(&data)?;
+        Ok(Valid(data))
+    }
+
+    fn validate_as_ref(data: &SectionConfigData<OspfSectionConfig>) -> Result<(), anyhow::Error> {
+        let mut fabrics = HashMap::new();
+        let mut nodes = Vec::new();
+
+        for (_, section) in data {
+            match section {
+                OspfSectionConfig::Node(node) => {
+                    nodes.push(node);
+                },
+                OspfSectionConfig::Fabric(fabric) => {
+                    fabrics.insert(&fabric.area, fabric);
+                }
+            }
+        }
+
+        let mut router_ids = HashSet::new();
+
+        for node in nodes {
+            let fabric = fabrics.get(&node.node_id.area)
+                .ok_or_else(|| anyhow!("verification error - missing fabric configuration"))?;
+
+            if !router_ids.insert(node.router_id) {
+                bail!("verification error - duplicate router_id");
+            }
+
+            if !fabric.loopback_prefix.contains_address(&node.router_id) {
+                bail!("Loopback IP of node is not contained in Loopback IP prefix");
+            }
+        }
+
+        Ok(())
+    }
+}
diff --git a/proxmox-ve-config/src/sdn/mod.rs b/proxmox-ve-config/src/sdn/mod.rs
index 25ed7e476b9f..515ce354f366 100644
--- a/proxmox-ve-config/src/sdn/mod.rs
+++ b/proxmox-ve-config/src/sdn/mod.rs
@@ -1,5 +1,6 @@
 pub mod config;
 pub mod ipam;
+pub mod fabric;
 
 use std::{error::Error, fmt::Display, str::FromStr};
 
-- 
2.39.5



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel