public inbox for pdm-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Christoph Heiss <c.heiss@proxmox.com>
To: pdm-devel@lists.proxmox.com
Subject: [PATCH datacenter-manager 1/5] config: auto-install: add new options for configuring vlan and bond
Date: Mon,  2 Nov 2026 13:49:33 +0100	[thread overview]
Message-ID: <20261102124939.1023342-2-c.heiss@proxmox.com> (raw)
In-Reply-To: <20261102124939.1023342-1-c.heiss@proxmox.com>

This mostly mirror the the options available from
`proxmox_installer_types::NetworkVlanOptions` and
`proxmox_installer_types::NetworkBondOptions`.

For bonding, an additional, explicit on/off setting is required to
properly keep UI state, and only udev filters are supported, not fixed
member lists.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
 Cargo.toml                         |   2 +-
 lib/pdm-config/Cargo.toml          |   1 +
 lib/pdm-config/src/auto_install.rs | 138 +++++++++++++++++++++++++++++
 3 files changed, 140 insertions(+), 1 deletion(-)

diff --git a/Cargo.toml b/Cargo.toml
index d9e7dece..b8450a1d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -68,7 +68,7 @@ proxmox-tfa = { version = "6", features = [ "api-types" ], default-features = fa
 proxmox-time = "2"
 proxmox-upgrade-checks = "1.1.1"
 proxmox-uuid = "1"
-proxmox-installer-types = "0.2"
+proxmox-installer-types = "0.3"
 proxmox-network-types = "1.1"
 
 # other proxmox crates
diff --git a/lib/pdm-config/Cargo.toml b/lib/pdm-config/Cargo.toml
index 8c1b46da..af077a62 100644
--- a/lib/pdm-config/Cargo.toml
+++ b/lib/pdm-config/Cargo.toml
@@ -26,6 +26,7 @@ proxmox-sys = { workspace = true, features = [ "acl", "crypt", "timer" ] }
 proxmox-acme-api.workspace = true
 proxmox-serde.workspace = true
 proxmox-installer-types = { workspace = true, features = [ "api-types" ] }
+proxmox-network-api.workspace = true
 proxmox-network-types.workspace = true
 pdm-api-types.workspace = true
 pdm-buildcfg.workspace = true
diff --git a/lib/pdm-config/src/auto_install.rs b/lib/pdm-config/src/auto_install.rs
index 8ac48fb6..ccb9eaa6 100644
--- a/lib/pdm-config/src/auto_install.rs
+++ b/lib/pdm-config/src/auto_install.rs
@@ -38,6 +38,7 @@ pub mod types {
         },
     };
     use proxmox_installer_types::answer::SUBSCRIPTION_KEY_SCHEMA;
+    use proxmox_network_api::{BondXmitHashPolicy, LinuxBondMode};
     use proxmox_schema::{ApiStringFormat, ApiType, PropertyString, api};
 
     #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
@@ -124,6 +125,19 @@ pub mod types {
                 type: String,
                 optional: true,
             },
+            "bond-enabled": {
+                optional: true,
+            },
+            "bond-members-filter": {
+                type: String,
+                optional: true,
+            },
+            "vlan-id": {
+                type: Integer,
+                optional: true,
+                minimum: 2,
+                maximum: 4094,
+            },
             filesystem: {
                 type: String,
             },
@@ -256,6 +270,26 @@ pub mod types {
         /// Whether to enable network interface name pinning.
         pub netif_name_pinning_enabled: bool,
 
+        /// Whether to enable interface bonding for the management network.
+        #[serde(default)]
+        pub bond_enabled: bool,
+        /// Netdev filter for selecting management network bond members
+        #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
+        pub bond_members_filter: PropertyString<BTreeMapWrapper<String>>,
+        /// Whether it is enough that any filter matches on a network interface or all given
+        /// filters must match to select an interface as bond member.
+        #[serde(default, skip_serializing_if = "Option::is_none")]
+        pub bond_members_filter_match: Option<answer::FilterMatch>,
+        /// Mode of the management interface bond.
+        #[serde(default, skip_serializing_if = "Option::is_none")]
+        pub bond_mode: Option<LinuxBondMode>,
+        /// Hash policy of the management interface bond.
+        #[serde(default, skip_serializing_if = "Option::is_none")]
+        pub bond_hash_policy: Option<BondXmitHashPolicy>,
+        /// VLAN tag to configure on the management interface.
+        #[serde(default, skip_serializing_if = "Option::is_none")]
+        pub vlan_id: Option<u16>,
+
         /// Root filesystem options.
         pub filesystem: PropertyString<answer::FilesystemOptions>,
 
@@ -319,6 +353,12 @@ pub mod types {
                 dns: conf.dns,
                 netdev_filter: PropertyString::new(BTreeMapWrapper(conf.netdev_filter)),
                 netif_name_pinning_enabled: conf.netif_name_pinning_enabled,
+                bond_enabled: conf.bond_enabled,
+                bond_members_filter: PropertyString::new(BTreeMapWrapper(conf.bond_members_filter)),
+                bond_members_filter_match: conf.bond_members_filter_match,
+                bond_mode: conf.bond_mode,
+                bond_hash_policy: conf.bond_hash_policy,
+                vlan_id: conf.vlan_id,
                 // disk options
                 filesystem: PropertyString::new(conf.filesystem),
                 disk_mode: conf.disk_mode,
@@ -372,6 +412,12 @@ pub mod types {
                 dns: self.dns,
                 netdev_filter: self.netdev_filter.into_inner().0,
                 netif_name_pinning_enabled: self.netif_name_pinning_enabled,
+                bond_enabled: self.bond_enabled,
+                bond_members_filter: self.bond_members_filter.into_inner().0,
+                bond_members_filter_match: self.bond_members_filter_match,
+                bond_mode: self.bond_mode,
+                bond_hash_policy: self.bond_hash_policy,
+                vlan_id: self.vlan_id,
                 // disk options
                 filesystem: self.filesystem.into_inner(),
                 disk_mode: self.disk_mode,
@@ -660,3 +706,95 @@ fn write_token_shadow(data: HashMap<String, String>) -> Result<()> {
     let raw = serde_json::to_string(&data)?;
     replace_config(TOKENS_SHADOW_FILE, raw.as_bytes())
 }
+
+#[cfg(test)]
+mod tests {
+    use serde_json::json;
+
+    use super::types::*;
+    use super::*;
+    use pdm_api_types::auto_installer::PreparedInstallationConfig;
+
+    fn prepared_config(extra: serde_json::Value) -> PreparedInstallationConfig {
+        let mut value = json!({
+            "id": "test",
+            "is-default": true,
+            "country": "at",
+            "fqdn": "test.example.com",
+            "use-dhcp-fqdn": false,
+            "keyboard": "en-us",
+            "mailto": "root@example.com",
+            "timezone": "UTC",
+            "reboot-on-error": false,
+            "reboot-mode": "reboot",
+            "use-dhcp-network": false,
+            "cidr": "192.168.100.2/24",
+            "gateway": "192.168.100.1",
+            "dns": "192.168.100.254",
+            "netif-name-pinning-enabled": false,
+            "filesystem": { "filesystem": "ext4" },
+            "disk-mode": "fixed",
+        });
+
+        for (key, v) in extra.as_object().unwrap() {
+            value
+                .as_object_mut()
+                .unwrap()
+                .insert(key.clone(), v.clone());
+        }
+
+        serde_json::from_value(value).unwrap()
+    }
+
+    fn round_trip(conf: PreparedInstallationConfig) -> PreparedInstallationConfig {
+        let mut data: SectionConfigData<PreparedInstallationSectionConfigWrapper> =
+            HashMap::new().into();
+        data.insert(conf.id.clone(), conf.clone().try_into().unwrap());
+
+        let raw = PreparedInstallationSectionConfigWrapper::write_section_config(
+            PREPARED_CONF_FILE,
+            &data,
+        )
+        .unwrap();
+        let parsed = PreparedInstallationSectionConfigWrapper::parse_section_config(
+            PREPARED_CONF_FILE,
+            &raw,
+        )
+        .unwrap();
+
+        match parsed.get(&conf.id).expect("entry survived the round trip") {
+            PreparedInstallationSectionConfigWrapper::PreparedConfig(section) => {
+                section.clone().try_into().unwrap()
+            }
+        }
+    }
+
+    #[test]
+    fn bond_and_vlan_survive_the_config_file_round_trip() {
+        let conf = prepared_config(json!({
+            "bond-members": ["bc:24:11:49:8d:75", "bc:24:11:49:8d:76"],
+            "bond-mode": "802.3ad",
+            "bond-hash-policy": "layer2+3",
+            "vlan-id": 100,
+        }));
+
+        let back = round_trip(conf.clone());
+
+        assert_eq!(conf.bond_members_filter, back.bond_members_filter);
+        assert_eq!(conf.bond_mode, back.bond_mode);
+        assert_eq!(conf.bond_hash_policy, back.bond_hash_policy);
+        assert_eq!(conf.vlan_id, back.vlan_id);
+        assert_eq!(conf, back);
+    }
+
+    #[test]
+    fn absent_bond_and_vlan_stay_absent() {
+        let conf = prepared_config(json!({}));
+        let back = round_trip(conf.clone());
+
+        assert!(back.bond_members_filter.is_empty());
+        assert_eq!(back.bond_mode, None);
+        assert_eq!(back.vlan_id, None);
+        assert_eq!(conf, back);
+    }
+}
-- 
2.55.0





  reply	other threads:[~2026-11-02 12:50 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-02 12:49 [PATCH datacenter-manager 0/5] auto-install: add support for VLAN and bond configurations Christoph Heiss
2026-11-02 12:49 ` Christoph Heiss [this message]
2026-11-02 12:49 ` [PATCH datacenter-manager 2/5] api-types: auto-installer: add new options for configuring vlan and bond Christoph Heiss
2026-11-02 12:49 ` [PATCH datacenter-manager 3/5] api: auto-installer: wire up new vlan and bond network options Christoph Heiss
2026-11-02 12:49 ` [PATCH datacenter-manager 4/5] ui: auto-installer: use .to_string() instead of manual serde variant Christoph Heiss
2026-11-02 12:49 ` [PATCH datacenter-manager 5/5] ui: auto-installer: add vlan and bond config as advanced options Christoph Heiss

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261102124939.1023342-2-c.heiss@proxmox.com \
    --to=c.heiss@proxmox.com \
    --cc=pdm-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal