all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Christoph Heiss <c.heiss@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH installer v5 10/23] gui: network: add bond setup options to advanced options dialog
Date: Fri,  6 Nov 2026 14:10:39 +0100	[thread overview]
Message-ID: <20261106131058.804870-11-c.heiss@proxmox.com> (raw)
In-Reply-To: <20261106131058.804870-1-c.heiss@proxmox.com>

Partially fixes #2164 [0].

Adds a second frame to the advanced network options dialog, allowing to
enable bonding and setting all the required options for it; such as
members, mode, hash policy and primary bond member, as required.

[0] https://bugzilla.proxmox.com/show_bug.cgi?id=2164

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
Changes v4 -> v5:
  * no changes

Changes v3 -> v4:
  * don't expand checkbox and grid, which resulted in unnecessary padding
    and dialog size

Changes v2 -> v3:
  * incorporate changes from @Thomas;
  * resolve the primary interface name to its MAC address also without
    pinning
  * clear primary interface if empty or not applicable to the mode
  * keep the selected physical management interface instead of
    overriding it with 'bond0', so that going back and disabling bonding
    works,
   * list the interfaces sorted
   * only show the bond summary in place of the interface selection if
     bonding is enabled
   * use the current pinned names for the member labels, only from
     physical links, and keep underscores, e.g. in driver names, in them

Changes v1 -> v2:
  * fix passing an undefined index to GTK when having a bond setup and
    switching between installer steps

 proxinstall | 292 +++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 257 insertions(+), 35 deletions(-)

diff --git a/proxinstall b/proxinstall
index 9c1e5ac..a676018 100755
--- a/proxinstall
+++ b/proxinstall
@@ -85,7 +85,9 @@ my @steps = (
 );
 
 # GUI global variables
-my $gtk_state = {};
+my $gtk_state = {
+    network_bonding_enabled => 0,
+};
 
 my $target_hds; # only for the summary view
 
@@ -349,6 +351,35 @@ my $create_basic_grid = sub {
     return $grid;
 };
 
+# takes an array ref of rows with [$label_text, $widget, $suffix_label] array refs as columns
+# $suffix_label is optional
+my $create_label_widget_grid = sub {
+    my ($labeled_widgets) = @_;
+
+    my $grid = &$create_basic_grid();
+
+    for (my $row = 0; $row < scalar($labeled_widgets->@*); $row++) {
+        my ($label_text, $widget, $suffix_label) = $labeled_widgets->[$row]->@*;
+
+        my $label = Gtk3::Label->new($label_text);
+        $label->set_visible(1);
+        $label->set_xalign(1.0);
+        $grid->attach($label, 0, $row, 1, 1);
+
+        $widget->set_visible(1);
+        $grid->attach($widget, 1, $row, 1, 1);
+
+        if ($suffix_label) {
+            my $suffix_label = Gtk3::Label->new($suffix_label);
+            $suffix_label->set_visible(1);
+            $suffix_label->set_xalign(1.0);
+            $grid->attach($suffix_label, 2, $row, 1, 1);
+        }
+    }
+
+    return $grid;
+};
+
 my sub create_network_interface_pin_frame {
     my ($on_enable_changed) = @_;
 
@@ -439,6 +470,177 @@ my sub create_network_interface_pin_frame {
     return ($frame, $on_done);
 }
 
+my sub create_network_bond_frame {
+    my $frame = Gtk3::Frame->new('Management Bond');
+
+    my $vbox = Gtk3::Box->new('vertical', 5);
+    $frame->add($vbox);
+    $vbox->set_margin_start(10);
+    $vbox->set_margin_end(10);
+    $vbox->set_margin_top(5);
+    $vbox->set_margin_bottom(5);
+
+    my $enable_checkbox = Gtk3::CheckButton->new('Enable bonding for management interface');
+    $enable_checkbox->set_active($gtk_state->{network_bonding_enabled});
+    $vbox->pack_start($enable_checkbox, 1, 0, 5);
+
+    my $bond_opts = Proxmox::Install::Config::get_mngmt_bond_opt();
+    my $interfaces = Proxmox::Install::RunEnv::get()->{network}->{interfaces};
+
+    # name the interface will have on the installed system
+    my $get_ifname = sub {
+        my ($iface) = @_;
+
+        return $iface->{name} if !$gtk_state->{network_pinning_enabled};
+
+        my $mapping = Proxmox::Install::Config::get_network_interface_pin_map();
+        return $mapping->{ $iface->{mac} } // $iface->{name};
+    };
+
+    my $mode_combo = Gtk3::ComboBoxText->new();
+    my @modes = qw(active-backup balance-alb balance-rr balance-tlb balance-xor broadcast 802.3ad);
+    for my $m (@modes) {
+        $mode_combo->append_text($m);
+    }
+    for my $i (0 .. $#modes) {
+        if ($modes[$i] eq $bond_opts->{mode}) {
+            $mode_combo->set_active($i);
+            last;
+        }
+    }
+
+    my $hash_policy_combo = Gtk3::ComboBoxText->new();
+    my @hash_policies = qw(layer2 layer2+3 layer3+4);
+    for my $p (@hash_policies) {
+        $hash_policy_combo->append_text($p);
+    }
+    for my $i (0 .. $#hash_policies) {
+        if ($hash_policies[$i] eq $bond_opts->{hash_policy}) {
+            $hash_policy_combo->set_active($i);
+            last;
+        }
+    }
+
+    my $primary_if_input = Gtk3::Entry->new();
+    $primary_if_input->set_tooltip_text('Name of the primary interface to set on the bond.');
+
+    if (defined(my $primary_mac = $bond_opts->{primary_interface})) {
+        my ($primary) = grep { lc($_->{mac}) eq lc($primary_mac) } values $interfaces->%*;
+        $primary_if_input->set_text($get_ifname->($primary)) if defined($primary);
+    }
+
+    my $on_mode_changed = sub {
+        my $mode = $mode_combo->get_active_text();
+        $hash_policy_combo->set_sensitive($mode eq '802.3ad' || $mode eq 'balance-xor');
+        $primary_if_input->set_sensitive($mode eq 'active-backup');
+    };
+    $mode_combo->signal_connect(changed => $on_mode_changed);
+
+    my $opts_grid = $create_label_widget_grid->([
+        ['Bond Mode', $mode_combo],
+        ['Hash Policy', $hash_policy_combo],
+        ['Primary Interface', $primary_if_input],
+    ]);
+
+    $vbox->pack_start($opts_grid, 0, 0, 5);
+
+    my $if_label = Gtk3::Label->new('Bond Interfaces:');
+    $if_label->set_xalign(0.0);
+    $if_label->set_margin_top(5);
+    $vbox->pack_start($if_label, 0, 0, 0);
+
+    my $checkboxes = {};
+    for my $ifname (sort keys $interfaces->%*) {
+        my $iface = $interfaces->{$ifname};
+        next if !defined($iface->{pinned_id});
+
+        # without a label, as one passed here would be parsed for mnemonics, eating underscores
+        my $checkbox = Gtk3::CheckButton->new();
+
+        my $is_member = grep { $_ eq $iface->{mac} } $bond_opts->{members}->@*;
+        $checkbox->set_active($is_member);
+
+        $vbox->pack_start($checkbox, 0, 0, 5);
+        $checkboxes->{ $iface->{mac} } = $checkbox;
+    }
+
+    my $set_sensitive = sub {
+        my ($active) = @_;
+
+        $opts_grid->foreach(sub {
+            $_[0]->set_sensitive($active);
+        });
+
+        # if activating all inputs, observe the rules applied by the mode change handler
+        $on_mode_changed->() if $active;
+
+        $if_label->set_sensitive($active);
+        for my $cb (values %$checkboxes) {
+            $cb->set_sensitive($active);
+        }
+    };
+    $set_sensitive->($gtk_state->{network_bonding_enabled});
+
+    $enable_checkbox->signal_connect(
+        toggled => sub {
+            my $active = !!$enable_checkbox->get_active();
+
+            $gtk_state->{network_bonding_enabled} = $active;
+            $set_sensitive->($active);
+        },
+    );
+
+    my $refresh_if_names = sub {
+        for my $iface (values $interfaces->%*) {
+            # virtual links can share the MAC address with a physical one
+            next if !defined($iface->{pinned_id}) || !defined($checkboxes->{ $iface->{mac} });
+
+            my $name = $get_ifname->($iface);
+            my $label = "$name ($iface->{mac}, $iface->{driver}, $iface->{state})";
+            $checkboxes->{ $iface->{mac} }->set_label($label);
+        }
+    };
+    $refresh_if_names->();
+
+    my $on_done = sub {
+        if (!$enable_checkbox->get_active()) {
+            Proxmox::Install::Config::set_mngmt_bond_opt('members', []);
+            return;
+        }
+
+        my @members = grep { $checkboxes->{$_}->get_active() } sort keys $checkboxes->%*;
+        die "at least 2 member interfaces are required for setting up bond\n"
+            if scalar(@members) < 2;
+
+        my $mode = $mode_combo->get_active_text();
+
+        # resolve the name to its MAC address, only applicable to the active-backup mode
+        my $primary_mac;
+        my $primary_name = $primary_if_input->get_text();
+        if ($mode eq 'active-backup' && length($primary_name) > 0) {
+            ($primary_mac) =
+                map { $_->{mac} }
+                grep { $get_ifname->($_) eq $primary_name } values $interfaces->%*;
+
+            die "unknown primary interface '$primary_name'\n" if !defined($primary_mac);
+            die "primary interface '$primary_name' is not a member of the bond\n"
+                if !grep { $_ eq $primary_mac } @members;
+        }
+
+        # unset the primary interface first, it is validated against the new members
+        Proxmox::Install::Config::set_mngmt_bond_opt('primary_interface', undef);
+        Proxmox::Install::Config::set_mngmt_bond_opt('members', \@members);
+        Proxmox::Install::Config::set_mngmt_bond_opt('mode', $mode);
+        Proxmox::Install::Config::set_mngmt_bond_opt(
+            'hash_policy',
+            $hash_policy_combo->get_active_text(),
+        );
+        Proxmox::Install::Config::set_mngmt_bond_opt('primary_interface', $primary_mac);
+    };
+
+    return ($frame, $refresh_if_names, $on_done);
+}
+
 my sub create_network_advanced_options_view {
     my ($done_cb) = @_;
 
@@ -457,9 +659,11 @@ my sub create_network_advanced_options_view {
 
     my $hbox = Gtk3::Box->new('vertical', 0);
 
-    my ($pin_frame, $pin_on_done) = create_network_interface_pin_frame(sub {});
+    my ($bond_frame, $bond_refresh, $bond_on_done) = create_network_bond_frame();
+    my ($pin_frame, $pin_on_done) = create_network_interface_pin_frame($bond_refresh);
 
     $hbox->pack_start($pin_frame, 0, 0, 10);
+    $hbox->pack_start($bond_frame, 0, 0, 10);
 
     $scrolled_window->add($hbox);
     $content->pack_start($scrolled_window, 1, 1, 10);
@@ -469,6 +673,7 @@ my sub create_network_advanced_options_view {
         response => sub {
             eval {
                 $pin_on_done->();
+                $bond_on_done->();
             };
             if ($@) {
                 Proxmox::UI::message($@, $dialog);
@@ -544,7 +749,29 @@ sub create_ipconf_view {
     $device_cb->pack_start($cell, 0);
     $device_cb->add_attribute($cell, 'text', 1);
 
+    my $device_bond_label = Gtk3::Label->new('bond0');
+    $device_bond_label->set_justify('center');
+    $device_bond_label->set_margin_top(5);
+    $device_bond_label->set_margin_bottom(5);
+
     my $refresh_device_cb = sub {
+        # with bonding, show its summary in place of the interface selection
+        my $bond_active = Proxmox::Install::Config::get_mngmt_bond_enabled();
+        if ($bond_active) {
+            my $bond_opts = Proxmox::Install::Config::get_mngmt_bond_opt();
+
+            $device_bond_label->set_text(
+                "bond0 (mode: $bond_opts->{mode}, "
+                    . scalar($bond_opts->{members}->@*)
+                    . " members)",
+            );
+        }
+
+        $device_cb->set_visible(!$bond_active);
+        $device_cb->set_no_show_all($bond_active);
+        $device_bond_label->set_visible($bond_active);
+        $device_bond_label->set_no_show_all(!$bond_active);
+
         # clear all entries and re-add them with their new names
         my $active = $device_cb->get_active();
         $device_model->clear();
@@ -682,6 +909,7 @@ sub create_ipconf_view {
 
     $grid->attach($label, 0, 0, 1, 1);
     $grid->attach($device_cb, 1, 0, 2, 1);
+    $grid->attach($device_bond_label, 1, 0, 2, 1);
 
     my $fqdn = Proxmox::Install::Config::get_fqdn();
     my $hostname = $run_env->{network}->{hostname} || $iso_env->{product};
@@ -805,11 +1033,34 @@ sub create_ack_view {
     my $country = Proxmox::Install::Config::get_country();
 
     my $mngmt_nic = Proxmox::Install::Config::get_mngmt_nic();
-    my $iface = Proxmox::Install::RunEnv::get('network')->{interfaces}->{$mngmt_nic};
-
+    my $all_ifaces = Proxmox::Install::RunEnv::get('network')->{interfaces};
+    my $iface = $all_ifaces->{$mngmt_nic};
     my $nic_mapping = Proxmox::Install::Config::get_network_interface_pin_map();
-    my $interface =
-        $gtk_state->{network_pinning_enabled} ? $nic_mapping->{ $iface->{mac} } : $iface->{name};
+
+    my $bond_opts = Proxmox::Install::Config::get_mngmt_bond_opt();
+
+    my $interface;
+    if (Proxmox::Install::Config::get_mngmt_bond_enabled()) {
+        my @member_names;
+
+        # translate all bond members MACs to their final interface name, either pinned or not
+        for my $mac ($bond_opts->{members}->@*) {
+            if ($gtk_state->{network_pinning_enabled}) {
+                push @member_names, $nic_mapping->{$mac};
+            } else {
+                my ($if) = grep { $_->{mac} eq $mac } values %$all_ifaces;
+                push @member_names, $if->{name};
+            }
+        }
+
+        my $members = join(' | ', @member_names);
+        $interface = "bond0 (mode: $bond_opts->{mode}, interfaces: $members)";
+    } else {
+        $interface =
+            $gtk_state->{network_pinning_enabled}
+            ? $nic_mapping->{ $iface->{mac} }
+            : $iface->{name};
+    }
 
     my %config_values = (
         __target_hd__ => join(' | ', $target_hds->@*),
@@ -1225,35 +1476,6 @@ my $target_hd_label;
 
 my $hdoption_first_setup = 1;
 
-# takes an array ref of rows with [$label_text, $widget, $suffix_label] array refs as columns
-# $suffix_label is optional
-my $create_label_widget_grid = sub {
-    my ($labeled_widgets) = @_;
-
-    my $grid = &$create_basic_grid();
-
-    for (my $row = 0; $row < scalar($labeled_widgets->@*); $row++) {
-        my ($label_text, $widget, $suffix_label) = $labeled_widgets->[$row]->@*;
-
-        my $label = Gtk3::Label->new($label_text);
-        $label->set_visible(1);
-        $label->set_xalign(1.0);
-        $grid->attach($label, 0, $row, 1, 1);
-
-        $widget->set_visible(1);
-        $grid->attach($widget, 1, $row, 1, 1);
-
-        if ($suffix_label) {
-            my $suffix_label = Gtk3::Label->new($suffix_label);
-            $suffix_label->set_visible(1);
-            $suffix_label->set_xalign(1.0);
-            $grid->attach($suffix_label, 2, $row, 1, 1);
-        }
-    }
-
-    return $grid;
-};
-
 # only relevant for raid with its multipl diskX to diskY mappings.
 my $get_selected_hdsize = sub {
     my $hdsize = shift;
-- 
2.56.0





  parent reply	other threads:[~2026-11-06 13:12 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-06 13:10 [PATCH installer v5 00/23] fix #2164: add bond and vlan setup across all installers Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 01/23] install: run make tidy Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 02/23] tree-wide: use `MacAddress` type instead of string for MAC addresses Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 03/23] install: factor out ifupdown2 config generation into own subroutine Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 04/23] install: do not rely on interface name map to be fully populated Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 05/23] install: config: add option for setting up bond on management interface Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 06/23] install: config: add option for setting up vlan " Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 07/23] install: network: set up management bond if requested Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 08/23] install: network: set up vlan on management interface " Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 09/23] gui: network: factor out name pinning into advanced options dialog Christoph Heiss
2026-11-06 13:10 ` Christoph Heiss [this message]
2026-11-06 13:10 ` [PATCH installer v5 11/23] gui: network: add vlan setup options to advanced network dialog Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 12/23] auto: pass through management network bond options to low-level installer Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 13/23] auto: pass through management network vlan " Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 14/23] tui: drop long obsolete comment about logo formatting Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 15/23] tui: views: add widget for displaying a group of checkboxes Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 16/23] tui: network: factor out name pinning into advanced options dialog Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 17/23] tui: network: match form label style when enabling/disabling elements Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 18/23] tui: network: add bond setup options to advanced network dialog Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 19/23] tui: network: add vlan " Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 20/23] gui: network: split advanced options dialog into tabs Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 21/23] tui: " Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 22/23] gui: network: list bond member candidates sharing a MAC address once Christoph Heiss
2026-11-06 13:10 ` [PATCH installer v5 23/23] tui: " 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=20261106131058.804870-11-c.heiss@proxmox.com \
    --to=c.heiss@proxmox.com \
    --cc=pve-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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal