[pve-devel] [PATCH proxmox-perl-rs v4 4/5] pve-rs: sdn: fabrics: add helper to generate ifupdown2 configuration
Gabriel Goller
g.goller at proxmox.com
Fri Jul 4 18:15:38 CEST 2025
>> [snip]
>> diff --git a/pve-rs/src/bindings/sdn/fabrics.rs b/pve-rs/src/bindings/sdn/fabrics.rs
>> index a7a740f5aac9..099c1a7ab515 100644
>> --- a/pve-rs/src/bindings/sdn/fabrics.rs
>> +++ b/pve-rs/src/bindings/sdn/fabrics.rs
>> @@ -6,6 +6,8 @@ pub mod pve_rs_sdn_fabrics {
>> //! / writing the configuration, as well as for generating ifupdown2 and FRR configuration.
>>
>> use std::collections::{BTreeMap, HashSet};
>> + use std::fmt::Write;
>> + use std::net::IpAddr;
>> use std::ops::Deref;
>> use std::sync::Mutex;
>>
>> @@ -15,6 +17,7 @@ pub mod pve_rs_sdn_fabrics {
>>
>> use perlmod::Value;
>> use proxmox_frr::serializer::to_raw_config;
>> + use proxmox_network_types::ip_address::Cidr;
>> use proxmox_section_config::typed::SectionConfigData;
>> use proxmox_ve_config::common::valid::Validatable;
>>
>> @@ -349,4 +352,105 @@ pub mod pve_rs_sdn_fabrics {
>>
>> to_raw_config(&frr_config)
>> }
>> +
>> + /// Helper method to generate the default /e/n/i config for a given CIDR.
>
>^ Not sure we want to shorten it like that, but at least put backticks
>around `/e/n/i` ;-)
Agree, changed to full path and added backticks :)
>> + fn render_interface(name: &str, cidr: Cidr, is_dummy: bool) -> Result<String, Error> {
>> + let mut interface = String::new();
>> +
>> + writeln!(interface)?;
>
>^ In our doc generator I recently removed all the leading newlines (and
>the trailing ones except for the one ending the final line) because the
>inconsistency across the building blocks became an unmaintainable mess.
Good point.
>Do we really want to start off with a newline here, rather than just say
>"this creates one stanza and it's the caller's responsibility to not
>merge it together with whatever comes before it"?
I could remove the newline here and then add it down below where this
helper is called.
Although this will be probably be reworked quite soon anyway as I think
for wireguard we'll implement some nicer ifupdown serialization
library thingy.
>Also this is IMO kind of a cryptic way (which includes error handling!)
>or starting with `let mut interface = "\n".to_string();`. (Or heck even
>`let interface = format!("\nauto {name}\n");`)
>
>> + writeln!(interface, "auto {name}")?;
>> + match cidr {
>> + Cidr::Ipv4(_) => writeln!(interface, "iface {name} inet static")?,
>> + Cidr::Ipv6(_) => writeln!(interface, "iface {name} inet6 static")?,
>> + }
>> + writeln!(interface, "\taddress {cidr}")?;
>> + if is_dummy {
>> + writeln!(interface, "\tlink-type dummy")?;
>> + }
>> + writeln!(interface, "\tip-forward 1")?;
>> +
>> + Ok(interface)
>> + }
>> +
>> + /// Class method: Generate the ifupdown2 configuration for a given node.
>> + #[export]
>> + fn get_interfaces_etc_network_config(
>> + #[try_from_ref] this: &PerlFabricConfig,
>> + node_id: NodeId,
>> + ) -> Result<String, Error> {
>> + let config = this.fabric_config.lock().unwrap();
>> + let mut interfaces = String::new();
>> +
>> + let node_fabrics = config.values().filter_map(|entry| {
>> + entry
>> + .get_node(&node_id)
>> + .map(|node| (entry.fabric(), node))
>> + .ok()
>> + });
>> +
>> + for (fabric, node) in node_fabrics {
>> + // dummy interface
>> + if let Some(ip) = node.ip() {
>> + let interface = render_interface(
>> + &format!("dummy_{}", fabric.id()),
>> + Cidr::new_v4(ip, 32)?,
>> + true,
>> + )?;
>> + write!(interfaces, "{interface}")?;
s/write/writeln
so that we have a newline after every interface definiton.
>> + }
>> + if let Some(ip6) = node.ip6() {
>> + let interface = render_interface(
>> + &format!("dummy_{}", fabric.id()),
>> + Cidr::new_v6(ip6, 128)?,
>> + true,
>> + )?;
>> + write!(interfaces, "{interface}")?;
>> + }
>> + match node {
>> + ConfigNode::Openfabric(node_section) => {
>> + for interface in node_section.properties().interfaces() {
>> + if let Some(ip) = interface.ip() {
>> + let interface =
>> + render_interface(interface.name(), Cidr::from(ip), false)?;
>> + write!(interfaces, "{interface}")?;
>> + }
>> + if let Some(ip) = interface.ip6() {
>> + let interface =
>> + render_interface(interface.name(), Cidr::from(ip), false)?;
>> + write!(interfaces, "{interface}")?;
>> + }
>> +
>> + // If not ip is configured, add auto and empty iface to bring interface up
>> + if let (None, None) = (interface.ip(), interface.ip6()) {
>> + writeln!(interfaces)?;
>> + writeln!(interfaces, "auto {}", interface.name())?;
>> + writeln!(interfaces, "iface {}", interface.name())?;
>> + writeln!(interfaces, "\tip-forward 1")?;
>> + }
>> + }
>> + }
>> + ConfigNode::Ospf(node_section) => {
>> + for interface in node_section.properties().interfaces() {
>> + if let Some(ip) = interface.ip() {
>> + let interface =
>> + render_interface(interface.name(), Cidr::from(ip), false)?;
>> + write!(interfaces, "{interface}")?;
>> + } else {
>> + let interface = render_interface(
>> + interface.name(),
>> + Cidr::from(IpAddr::from(
>> + node.ip()
>> + .ok_or(anyhow::anyhow!("there has to be a ipv4 address"))?,
>
>^ use `ok_or_else` here please, unless there's a documented guarantee
>that this is cheap and does not allocate?
Agree, added `ok_or_else`.
>> [snip]
Thanks for the review!
More information about the pve-devel
mailing list