[pve-devel] [PATCH proxmox-ve-rs 15/17] ve-config: add validation for section-config

Gabriel Goller g.goller at proxmox.com
Fri Mar 28 18:13:04 CET 2025


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 at proxmox.com>
Co-authored-by: Stefan Hanreich <s.hanreich at 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





More information about the pve-devel mailing list