From 5820b8752f4eefed560d78684f8029446826ea2a Mon Sep 17 00:00:00 2001 From: Volodymyr Katkalov Date: Wed, 11 Sep 2024 14:51:39 +0300 Subject: [PATCH] Universal Bonding Tests Co-authored-by: Santiago Zarate --- data/network_bonding/dnsmasq.conf | 5 + data/network_bonding/kea-dhcp4.conf | 28 ++ lib/network_utils.pm | 329 +++++++++++++++++- lib/registration.pm | 54 ++- .../extra_tests_network_bonding.yaml | 25 ++ schedule/microos/bonding.yaml | 6 - tests/console/suseconnect_scc.pm | 52 +-- tests/{microos => network}/network_bonding.pm | 113 +++--- tests/network/network_bonding_setup.pm | 286 +++++++++++++++ 9 files changed, 776 insertions(+), 122 deletions(-) create mode 100644 data/network_bonding/dnsmasq.conf create mode 100644 data/network_bonding/kea-dhcp4.conf create mode 100644 schedule/functional/extra_tests_network_bonding.yaml delete mode 100644 schedule/microos/bonding.yaml rename tests/{microos => network}/network_bonding.pm (52%) create mode 100644 tests/network/network_bonding_setup.pm diff --git a/data/network_bonding/dnsmasq.conf b/data/network_bonding/dnsmasq.conf new file mode 100644 index 000000000000..c9b5e38ae3f8 --- /dev/null +++ b/data/network_bonding/dnsmasq.conf @@ -0,0 +1,5 @@ +interface=eth0 +bind-interfaces +no-resolv +cache-size=4096 +server=8.8.8.8 \ No newline at end of file diff --git a/data/network_bonding/kea-dhcp4.conf b/data/network_bonding/kea-dhcp4.conf new file mode 100644 index 000000000000..edc7399064e4 --- /dev/null +++ b/data/network_bonding/kea-dhcp4.conf @@ -0,0 +1,28 @@ +{ + "Dhcp4": { + "interfaces-config": { + "interfaces": [ "eth0" ] + }, + "subnet4": [ + { + "id": 1, + "subnet": "10.0.2.0/24", + "pools": [ + { + "pool": "10.0.2.15 - 10.0.2.100" + } + ], + "option-data": [ + { + "name": "routers", + "data": "10.0.2.2" + }, + { + "name": "domain-name-servers", + "data": "10.0.2.101" + } + ] + } + ] + } +} diff --git a/lib/network_utils.pm b/lib/network_utils.pm index 3e349702a5e8..6f68e94288ed 100644 --- a/lib/network_utils.pm +++ b/lib/network_utils.pm @@ -19,8 +19,34 @@ use strict; use warnings; use testapi; use mm_network; - -our @EXPORT = qw(setup_static_network recover_network can_upload_logs iface ifc_exists ifc_is_up genmac); +use Utils::Systemd qw(systemctl); + +use utils qw(validate_script_output_retry); + +our @EXPORT = qw( + setup_static_network + recover_network + can_upload_logs + iface + ifc_exists + ifc_is_up + genmac + cidr_to_netmask + set_nics_link_speed_duplex + check_connectivity_to_host_with_retry + get_nics + is_nm_used + is_wicked_used + delete_all_existing_connections + create_bond + add_interfaces_to_bond + set_resolv + set_nic_dhcp_auto_nmcli + set_nic_dhcp_auto_wicked + set_nic_dhcp_auto + all_nics_have_ip + reload_connections_until_all_ips_assigned +); =head2 setup_static_network @@ -158,4 +184,303 @@ sub genmac { return lc(join(':', @mac)); } +=head2 cidr_to_netmask + +Converts CIDR notation to a netmask string in IPv4 address format. + +=cut + +sub cidr_to_netmask { + my ($cidr_str) = @_; + + $cidr_str =~ /(\d+)/; + my $cidr = $1; + + my $binmask = '1' x $cidr . '0' x (32 - $cidr); + my @octets = unpack("C4", pack("B32", $binmask)); + return join('.', @octets); +} + +=head2 set_nics_link_speed_duplex + +Sets the link speed, duplex settings, and autoneg status for specified NICs. +Accepts hash reference containing NICs and their settings. + + set_nics_link_speed_duplex({ + nics => ['eth0', 'eth1'], + speed => 1000, # Speed in Mbps + duplex => 'full', # Duplex type: 'full' or 'half' + autoneg => 'off' # Auto-negotiation: 'on' or 'off' + }); + +=cut + +sub set_nics_link_speed_duplex { + my ($args_ref) = @_; + my @nics = @{$args_ref->{nics}}; + my $speed = $args_ref->{speed} // 1000; # default speed 1000 Mbps if not specified + my $duplex = $args_ref->{duplex} // 'full'; # default to full duplex if not specified + my $autoneg = $args_ref->{autoneg} // 'off'; # default to autoneg off if not specified + + for my $nic (@nics) { + record_info("SET $nic", "Setting link speed to $speed, duplex to $duplex, and autoneg to $autoneg"); + script_run("ethtool -s $nic speed $speed duplex $duplex autoneg $autoneg"); + } +} + +=head2 check_connectivity_to_host_with_retry + +Checks connectivity from a specified bonding interface to a host. + +This function pings a designated host from a specified bonding interface. It uses the function +validate_script_output_retry to retry the ping command multiple times. + + check_connectivity_to_host_with_retry('bond0', '192.168.1.1'); + +=cut + +sub check_connectivity_to_host_with_retry { + my ($iface, $ping_host) = @_; + my $ping_command = "ping -c1 -I $iface $ping_host"; + + validate_script_output_retry( + $ping_command, + sub { m/1 packets transmitted, 1 received, 0% packet loss,/ }, + type_command => 1 + ); +} + +=head2 get_nics + +Retrieves a list of network interfaces, excluding specified ones and the loopback interface. + +This function scans for network interfaces available on the system, optionally ignoring specified interfaces +and always excluding the loopback interface. It's particularly useful for scripts that need to dynamically +determine which network interfaces to operate on, allowing for exclusion of interfaces that are not of interest. + + get_nics(['bond0', 'bond1']); + +=cut + +sub get_nics { + my ($ignore_ref) = @_; + my @ignore = @$ignore_ref; + + my $command = "ip -o link show | grep -v 'lo'"; + + foreach my $iface (@ignore) { + $command .= " | grep -v '$iface'"; + } + + $command .= " | awk -F: '{print \$2}' | awk '{print \$1}'"; + + my $result = script_output($command, type_command => 1); + + my @nics = split(/\n/, $result); + + record_info(scalar(@nics) . " NICs Detected", join(', ', @nics)); + + return @nics; +} + +=head2 is_nm_used + + Check if NetworkManager service is active. + +=cut + +sub is_nm_used { + return script_run("systemctl is-active NetworkManager") == 0; +} + +=head2 is_wicked_used + + Check if wicked service is active. + +=cut + +sub is_wicked_used { + return script_run("systemctl is-active wicked") == 0; +} + +sub delete_all_existing_connections_nm { + my $output = script_output('nmcli -g DEVICE,UUID conn show', type_command => 1); + my %seen_uuids; + + foreach my $line (split "\n", $output) { + next if $line =~ /^\s*$/; + + my ($device, $uuid) = split /:/, $line; + next if defined $device && $device eq 'lo'; + next if exists $seen_uuids{$uuid}; + + $seen_uuids{$uuid} = 1; + assert_script_run "nmcli con delete uuid '$uuid'"; + } +} + +sub delete_all_existing_connections_wicked { + assert_script_run "wicked ifdown all"; + script_run "rm -f /etc/sysconfig/network/ifcfg-*"; +} + +sub delete_all_existing_connections { + delete_all_existing_connections_nm() if is_nm_used(); + delete_all_existing_connections_wicked() if is_wicked_used(); +} + +sub create_bond { + my ($bond_name, $options) = @_; + my $bond_mode = $options->{mode}; + my $miimon = $options->{miimon} // 200; + my $autoconnect_slaves = $options->{autoconnect_slaves} // 1; + + if (is_nm_used()) { + assert_script_run "nmcli con add type bond ifname $bond_name con-name $bond_name bond.options \"mode=$bond_mode, miimon=$miimon\""; + assert_script_run "nmcli connection modify $bond_name connection.autoconnect-slaves $autoconnect_slaves"; + } + + if (is_wicked_used()) { + # Remove the old configuration file if it exists + script_run "rm -f /etc/sysconfig/network/ifcfg-$bond_name"; + + assert_script_run "echo 'STARTMODE=auto' > /etc/sysconfig/network/ifcfg-$bond_name"; + assert_script_run "echo 'BONDING_MASTER=yes' >> /etc/sysconfig/network/ifcfg-$bond_name"; + assert_script_run "echo 'BONDING_SLAVE=no' >> /etc/sysconfig/network/ifcfg-$bond_name"; + assert_script_run "echo 'BONDING_MODULE_OPTS=\"mode=$bond_mode miimon=$miimon\"' >> /etc/sysconfig/network/ifcfg-$bond_name"; + } +} + +sub add_interfaces_to_bond { + my ($bond_name, @interfaces) = @_; + if (is_nm_used()) { + foreach my $interface (@interfaces) { + assert_script_run "nmcli con add type ethernet ifname $interface master $bond_name"; + } + } + + if (is_wicked_used()) { + my $index = 1; + + foreach my $interface (@interfaces) { + # Remove the old configuration file for the interface if it exists + script_run "rm -f /etc/sysconfig/network/ifcfg-$interface"; + + # Create the new configuration file for the interface + assert_script_run "echo 'BOOTPROTO=static' > /etc/sysconfig/network/ifcfg-$interface"; + assert_script_run "echo 'STARTMODE=auto' >> /etc/sysconfig/network/ifcfg-$interface"; + assert_script_run "echo 'BONDING_MASTER=no' >> /etc/sysconfig/network/ifcfg-$interface"; + assert_script_run "echo 'BONDING_SLAVE=yes' >> /etc/sysconfig/network/ifcfg-$interface"; + assert_script_run "echo 'BONDING_MASTER_IF=$bond_name' >> /etc/sysconfig/network/ifcfg-$interface"; + + # Append slave interfaces to the bond configuration + assert_script_run "echo 'BONDING_SLAVE_$index=$interface' >> /etc/sysconfig/network/ifcfg-$bond_name"; + $index++; + } + } +} + +sub set_resolv { + my (%args) = @_; + $args{attempt_all_ns} //= 0; + my @nameservers = @{$args{nameservers}}; + + # Set DNS in /etc/resolv.conf + assert_script_run("rm /etc/resolv.conf || true"); + assert_script_run("touch /etc/resolv.conf"); + + foreach my $nameserver (@nameservers) { + assert_script_run("echo 'nameserver $nameserver' >> /etc/resolv.conf"); + } + + if ($args{attempt_all_ns}) { + # Set options + assert_script_run("echo 'options rotate' >> /etc/resolv.conf"); + assert_script_run("echo 'options timeout:2' >> /etc/resolv.conf"); + + # Set attempts based on the number of nameservers + my $attempts_count = scalar @nameservers; # Count the number of items in @nameservers + assert_script_run("echo 'options attempts:$attempts_count' >> /etc/resolv.conf"); + } +} + +sub set_nic_dhcp_auto_nmcli { + my ($nic) = @_; + assert_script_run "nmcli con add type ethernet ifname $nic con-name $nic"; + assert_script_run "nmcli con modify $nic ipv4.method auto connection.autoconnect yes"; +} + +sub set_nic_dhcp_auto_wicked { + my ($nic) = @_; + assert_script_run "echo 'BOOTPROTO=dhcp' > /etc/sysconfig/network/ifcfg-$nic"; + assert_script_run "echo 'STARTMODE=auto' >> /etc/sysconfig/network/ifcfg-$nic"; +} + +sub set_nic_dhcp_auto { + my ($nic) = @_; + set_nic_dhcp_auto_nmcli($nic) if is_nm_used(); + set_nic_dhcp_auto_wicked($nic) if is_wicked_used(); +} + +sub all_nics_have_ip { + my ($nics_ref) = @_; + my @nics = @$nics_ref; + + foreach my $nic (@nics) { + my $ip_output = script_output("ip addr show $nic"); + if ($ip_output !~ /inet\s+\d+\.\d+\.\d+\.\d+/) { + return 0; # If any NIC doesn't have an IP, return false + } + } + return 1; # All NICs have an IP +} + +sub reload_connections_until_all_ips_assigned { + my (%args) = @_; + $args{timeout} //= 180; + $args{retry_interval} //= 10; + + my @nics = @{$args{nics}}; + + my $reload_dhcp_fn = sub { + die 'Incompatible network manager'; + }; + + if (is_nm_used()) { + $reload_dhcp_fn = sub { + foreach my $nic (@nics) { + assert_script_run "nmcli con down $nic || true"; + assert_script_run "nmcli con up $nic"; + } + }; + } elsif (is_wicked_used()) { + $reload_dhcp_fn = sub { + systemctl 'restart wicked'; + }; + } + + # If a custom reload function is provided, use it + if ($args{reload_dhcp_fn}) { + $reload_dhcp_fn = $args{reload_dhcp_fn}; + } + + my $elapsed_time = 0; + + while ($elapsed_time < $args{timeout}) { + $reload_dhcp_fn->(); # Execute the provided action (e.g., restart service) + + # Check if all NICs have an IP + if (all_nics_have_ip(\@nics)) { + last; # All NICs have IP addresses, exit loop + } + + sleep $args{retry_interval}; + $elapsed_time += $args{retry_interval}; + + if ($elapsed_time >= $args{timeout}) { + die "Failed to obtain IPs for all interfaces within $args{timeout} seconds"; + } + } +} + 1; diff --git a/lib/registration.pm b/lib/registration.pm index bff981f40467..5b1822271802 100644 --- a/lib/registration.pm +++ b/lib/registration.pm @@ -10,8 +10,10 @@ use warnings; use testapi; use Utils::Architectures; use Utils::Backends qw(is_qemu); -use utils qw(addon_decline_license assert_screen_with_soft_timeout zypper_call systemctl handle_untrusted_gpg_key quit_packagekit script_retry wait_for_purge_kernels); -use version_utils qw(is_sle is_sles4sap is_upgrade is_leap_migration is_sle_micro is_hpc is_jeos is_transactional); +use serial_terminal 'select_serial_terminal'; +use utils qw(addon_decline_license assert_screen_with_soft_timeout zypper_call systemctl handle_untrusted_gpg_key quit_packagekit script_retry script_output_retry wait_for_purge_kernels); +use version_utils qw(is_sle is_sles4sap is_upgrade is_leap_migration is_sle_micro is_hpc is_jeos is_transactional is_staging); +use transactional qw(trup_call process_reboot); use constant ADDONS_COUNT => 50; use y2_module_consoletest; use YaST::workarounds; @@ -44,6 +46,7 @@ our @EXPORT = qw( register_addons handle_scc_popups process_modules + runtime_registration %SLE15_MODULES %SLE15_DEFAULT_MODULES %ADDONS_REGCODE @@ -1043,4 +1046,51 @@ sub process_modules { } } +sub runtime_registration { + return if get_var('HDD_SCC_REGISTERED'); + my $cmd = ' -r ' . get_required_var 'SCC_REGCODE'; + my $scc_addons = get_var 'SCC_ADDONS', ''; + # fake scc url pointing to synced repos on openQA + # valid only for products currently in development + # please unset in job def *SCC_URL* if not required + my $fake_scc = get_var 'SCC_URL', ''; + $cmd .= ' --url ' . $fake_scc if $fake_scc; + my $retries = 5; # number of retries to run SUSEConnect commands + my $delay = 60; # time between retries to run SUSEConnect commands + + + select_serial_terminal; + die 'SUSEConnect package is not pre-installed!' if script_run 'command -v SUSEConnect'; + if ((is_jeos || is_sle_micro)) { + my $status = script_output('SUSEConnect --status-text'); + die 'System has been already registered!' if ($status !~ m/not registered/i); + } + + # There are sporadic failures due to the command timing out, so we increase the timeout + # and make use of retries to overcome a possible sporadic network issue. + # script_output_retry is useless for `transactional-update` cmd because it returns 0 even with failure + # trup_call will raise a failure if the command fails + if (is_transactional) { + trup_call('register' . $cmd); + trup_call('--continue run zypper --gpg-auto-import-keys refresh') if is_staging; + add_suseconnect_product('SL-Micro-Extras') if (is_sle_micro('>=6.0')); + process_reboot(trigger => 1); + } + else { + my $output = script_output_retry("SUSEConnect $cmd", retry => $retries, delay => $delay, timeout => 180); + die($output) if ($output =~ m/error|timeout|problem retrieving/i); + } + # Check available extenstions (only present in sle) + my $extensions = script_output_retry("SUSEConnect --list-extensions", retry => $retries, delay => $delay, timeout => 180); + record_info('Extensions', $extensions); + + die("None of the modules are Activated") if ($extensions !~ m/Activated/ && is_sle); + + # add modules + register_addons_cmd($scc_addons, $retries) if $scc_addons; + # Check that repos actually work + zypper_call 'refresh'; + zypper_call 'repos --details'; +} + 1; diff --git a/schedule/functional/extra_tests_network_bonding.yaml b/schedule/functional/extra_tests_network_bonding.yaml new file mode 100644 index 000000000000..cb583cdb4fff --- /dev/null +++ b/schedule/functional/extra_tests_network_bonding.yaml @@ -0,0 +1,25 @@ +name: extra_tests_network_bonding +description: > + Maintainer: vkatkalov. + Extra bonding tests +conditional_schedule: + boot: + DISTRI: + 'microos': + - microos/disk_boot + 'sle-micro': + - microos/disk_boot + 'opensuse': + - boot/boot_to_desktop + - installation/bootloader_start + 'sle': + - boot/boot_to_desktop + - installation/bootloader_start + bonding: + HOSTNAME: + 'ping': + - network/network_bonding +schedule: + - '{{boot}}' + - network/network_bonding_setup + - '{{bonding}}' diff --git a/schedule/microos/bonding.yaml b/schedule/microos/bonding.yaml deleted file mode 100644 index cee7b55805e9..000000000000 --- a/schedule/microos/bonding.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -description: 'Network bonding test suite for ALP' -name: 'alp_bonding@x86_64' -schedule: - - microos/disk_boot - - microos/network_bonding diff --git a/tests/console/suseconnect_scc.pm b/tests/console/suseconnect_scc.pm index 8ca20d8fca5d..ce40f967aa44 100644 --- a/tests/console/suseconnect_scc.pm +++ b/tests/console/suseconnect_scc.pm @@ -9,58 +9,10 @@ use Mojo::Base qw(consoletest); use testapi; -use serial_terminal 'select_serial_terminal'; -use utils qw(zypper_call script_retry script_output_retry); -use version_utils qw(is_sle is_jeos is_sle_micro is_transactional is_staging); -use registration qw(register_addons_cmd verify_scc investigate_log_empty_license); -use transactional qw(trup_call process_reboot); -use registration; +use registration qw(verify_scc investigate_log_empty_license runtime_registration); sub run { - return if get_var('HDD_SCC_REGISTERED'); - my $cmd = ' -r ' . get_required_var 'SCC_REGCODE'; - my $scc_addons = get_var 'SCC_ADDONS', ''; - # fake scc url pointing to synced repos on openQA - # valid only for products currently in development - # please unset in job def *SCC_URL* if not required - my $fake_scc = get_var 'SCC_URL', ''; - $cmd .= ' --url ' . $fake_scc if $fake_scc; - my $retries = 5; # number of retries to run SUSEConnect commands - my $delay = 60; # time between retries to run SUSEConnect commands - - - select_serial_terminal; - die 'SUSEConnect package is not pre-installed!' if script_run 'command -v SUSEConnect'; - if ((is_jeos || is_sle_micro)) { - my $status = script_output('SUSEConnect --status-text'); - die 'System has been already registered!' if ($status !~ m/not registered/i); - } - - # There are sporadic failures due to the command timing out, so we increase the timeout - # and make use of retries to overcome a possible sporadic network issue. - # script_output_retry is useless for `transactional-update` cmd because it returns 0 even with failure - # trup_call will raise a failure if the command fails - if (is_transactional) { - trup_call('register' . $cmd); - trup_call('--continue run zypper --gpg-auto-import-keys refresh') if is_staging; - add_suseconnect_product('SL-Micro-Extras') if (is_sle_micro('>=6.0')); - process_reboot(trigger => 1); - } - else { - my $output = script_output_retry("SUSEConnect $cmd", retry => $retries, delay => $delay, timeout => 180); - die($output) if ($output =~ m/error|timeout|problem retrieving/i); - } - # Check available extenstions (only present in sle) - my $extensions = script_output_retry("SUSEConnect --list-extensions", retry => $retries, delay => $delay, timeout => 180); - record_info('Extensions', $extensions); - - die("None of the modules are Activated") if ($extensions !~ m/Activated/ && is_sle); - - # add modules - register_addons_cmd($scc_addons, $retries) if $scc_addons; - # Check that repos actually work - zypper_call 'refresh'; - zypper_call 'repos --details'; + runtime_registration(); # assume it will run in serial terminal } sub post_fail_hook { diff --git a/tests/microos/network_bonding.pm b/tests/network/network_bonding.pm similarity index 52% rename from tests/microos/network_bonding.pm rename to tests/network/network_bonding.pm index 1f0a5c307ca5..53345f404758 100644 --- a/tests/microos/network_bonding.pm +++ b/tests/network/network_bonding.pm @@ -13,61 +13,36 @@ use testapi; use power_action_utils "power_action"; use utils qw(validate_script_output_retry); use serial_terminal qw(select_serial_terminal); +use network_utils qw(get_nics cidr_to_netmask is_nm_used is_wicked_used check_connectivity_to_host_with_retry delete_all_existing_connections create_bond add_interfaces_to_bond set_nics_link_speed_duplex); +use lockapi; +use utils; +use console::ovs_utils; +use version_utils; -sub check_connectivity { - my ($bond_name) = @_; - my $ping_host = "conncheck.opensuse.org"; - my $ping_command = "ping -c1 -I $bond_name $ping_host"; +my $target_ip = "10.0.2.101"; - validate_script_output_retry( - $ping_command, - sub { m/1 packets transmitted, 1 received, 0% packet loss,/ } - ); -} - -sub get_nics { +sub configure_bond_interface_network { my ($bond_name) = @_; - my @devices; - - foreach my $line (split('\n', script_output('nmcli -g DEVICE conn show'))) { - next if $line =~ /^\s*$/; - # Skip the loopback device and the bond interface - next if $line eq 'lo'; - next if $line eq $bond_name; - - push @devices, $line; + if (is_nm_used()) { + # NetworkManager configuration using DHCP + assert_script_run "nmcli con modify $bond_name ipv4.method auto"; + assert_script_run "nmcli con up $bond_name"; } - return @devices; -} - -sub delete_existing_connections { - my $output = script_output('nmcli -g DEVICE,UUID conn show'); - my %seen_uuids; + if (is_wicked_used()) { + # Wicked configuration: setting to DHCP + assert_script_run "touch /etc/sysconfig/network/ifcfg-$bond_name"; + assert_script_run "echo 'BOOTPROTO=dhcp' >> /etc/sysconfig/network/ifcfg-$bond_name"; + assert_script_run "echo 'STARTMODE=auto' >> /etc/sysconfig/network/ifcfg-$bond_name"; - foreach my $line (split "\n", $output) { - next if $line =~ /^\s*$/; + my $route_config_file = "/etc/sysconfig/network/ifroute-$bond_name"; - my ($device, $uuid) = split /:/, $line; - next if defined $device && $device eq 'lo'; - next if exists $seen_uuids{$uuid}; + # Remove any existing route configuration since DHCP handles routes + script_run "rm -f $route_config_file"; - $seen_uuids{$uuid} = 1; - script_run "nmcli con delete uuid '$uuid'"; - } -} - -sub create_bond { - my ($bond_name, $bond_mode, $miimon) = @_; - assert_script_run "nmcli con add type bond ifname $bond_name con-name $bond_name bond.options \"mode=$bond_mode, miimon=$miimon\""; - assert_script_run "nmcli connection modify $bond_name connection.autoconnect-slaves 1"; -} - -sub add_devices_to_bond { - my ($bond_name, @devices) = @_; - foreach my $device (@devices) { - assert_script_run "nmcli con add type ethernet ifname $device master $bond_name"; + # Restart Wicked to apply the DHCP configuration + systemctl 'restart wicked'; } } @@ -83,9 +58,10 @@ sub test_failover { # Validate bond mode and NIC statuses (the downed NIC should be "down") validate_bond_mode_and_slaves($bond_name, $description, \@nics_status); - check_connectivity $bond_name; + check_connectivity_to_host_with_retry($bond_name, $target_ip); assert_script_run "ip link set dev $device up"; + systemctl 'restart wicked' if is_wicked_used(); } sub validate_bond_mode_and_slaves { @@ -99,7 +75,8 @@ sub validate_bond_mode_and_slaves { validate_script_output_retry( "grep -A 1 'Slave Interface: $device' /proc/net/bonding/$bond_name", - sub { m/MII Status: $expected_status/ } + sub { m/MII Status: $expected_status/ }, + type_command => 1 ); } } @@ -110,17 +87,30 @@ sub test_bonding_mode { select_serial_terminal; - delete_existing_connections; + delete_all_existing_connections; - create_bond($bond_name, $bond_mode, $miimon); - add_devices_to_bond($bond_name, @nics); + create_bond($bond_name, { + mode => $bond_mode, + miimon => $miimon, + autoconnect_slaves => 1 + }); - assert_script_run "nmcli con up $bond_name"; + add_interfaces_to_bond($bond_name, @nics); + configure_bond_interface_network($bond_name); power_action('reboot', textmode => 1); $self->wait_boot; select_serial_terminal; - check_connectivity $bond_name; + + # Because settings don't survive reboot, to fix bug on Network Manager + set_nics_link_speed_duplex({ + nics => \@nics, + speed => 1000, # assuming a speed of 1000 Mbps + duplex => 'full', # assuming full duplex + autoneg => 'off' # assuming autoneg is off + }); + + check_connectivity_to_host_with_retry($bond_name, $target_ip); # Validate that all NICs are "up" validate_bond_mode_and_slaves($bond_name, $description, [map { [$_, 1] } @nics]); @@ -128,28 +118,25 @@ sub test_bonding_mode { # Testing failover for each NIC test_failover($bond_mode, $bond_name, $_, $description, \@nics) for @nics; - delete_existing_connections; + delete_all_existing_connections; } sub run { my ($self) = @_; + select_serial_terminal; my $bond_name = "bond0"; my $miimon = 200; - my @nics = get_nics($bond_name); - - record_info(scalar(@nics) . " NICs Detected", join(', ', @nics)); + my @nics = get_nics([$bond_name]); my @bond_modes = ( ['balance-rr', 'load balancing (round-robin)'], ['active-backup', 'fault-tolerance (active-backup)'], ['balance-xor', 'load balancing (xor)'], ['broadcast', 'fault-tolerance (broadcast)'], - - # For mode=802.3ad|balance-tlb|balance-alb the switch must have support for mode and it has to be enabled on the switch - # ['802.3ad', '802.3ad'], - # ['balance-tlb', 'balance-tlb'], - # ['balance-alb', 'balance-alb'] + ['802.3ad', 'IEEE 802.3ad Dynamic link aggregation'], + ['balance-tlb', 'transmit load balancing'], + ['balance-alb', 'adaptive load balancing'] ); foreach my $mode_info (@bond_modes) { @@ -157,6 +144,8 @@ sub run { record_info("Testing Bonding Mode: $bond_mode", $description); test_bonding_mode($self, \@nics, $miimon, $bond_name, $bond_mode, $description); } + + barrier_wait "BONDING_TESTS_DONE"; } 1; diff --git a/tests/network/network_bonding_setup.pm b/tests/network/network_bonding_setup.pm new file mode 100644 index 000000000000..520d833b7af3 --- /dev/null +++ b/tests/network/network_bonding_setup.pm @@ -0,0 +1,286 @@ +# SUSE's openQA tests +# +# Copyright 2024 SUSE LLC +# SPDX-License-Identifier: FSFAP + +# Summary: Setup simple network topology for bonding tests +# +# Maintainer: QE Core +# +use base 'consoletest'; +use strict; +use warnings; +use testapi; +use power_action_utils "power_action"; +use serial_terminal 'select_serial_terminal'; +use network_utils qw(get_nics cidr_to_netmask is_nm_used is_wicked_used delete_all_existing_connections set_nics_link_speed_duplex check_connectivity_to_host_with_retry set_resolv set_nic_dhcp_auto reload_connections_until_all_ips_assigned); +use Utils::Systemd qw(disable_and_stop_service systemctl check_unit_file); +use lockapi; +use utils; +use version_utils; +use registration qw(runtime_registration); +use transactional qw(trup_call process_reboot); + +my $server_ip = "10.0.2.101"; +my $subnet = "/24"; +my $gateway = "10.0.2.2"; + +my $dhcpd_server_image_name = get_var "DHCPD_SERVER_IMAGE_NAME"; +my $dhcpd_server_image_registry = get_var "DHCPD_SERVER_IMAGE_REGISTRY"; + +my $public_dns_string = get_var "PUBLIC_DNS", "8.8.8.8,8.8.4.4"; +my $scc_dns_string = get_var "SCC_DNS", ""; +my $registry_dns_string = get_var "REGISTRY_DNS", ""; +my $container_primary_dns_string = get_var "CONTAINER_PRIMARY_DNS", "8.8.8.8,8.8.4.4"; +my $override_dns_string = get_var "OVERRIDE_DNS", ""; + +# Split the DNS strings into arrays only if the variable is defined and not empty +my @public_dns = defined($public_dns_string) && $public_dns_string ne "" ? split(",", $public_dns_string) : (); +my @scc_dns = defined($scc_dns_string) && $scc_dns_string ne "" ? split(",", $scc_dns_string) : (); +my @registry_dns = defined($registry_dns_string) && $registry_dns_string ne "" ? split(",", $registry_dns_string) : (); +my @container_primary_dns = defined($container_primary_dns_string) && $container_primary_dns_string ne "" ? split(",", $container_primary_dns_string) : (); + +my $requires_scc_registration = is_sle_micro || is_sle; + +sub install_pkgs { + my ($self, @pkgs) = @_; + my @to_install; # Array to store packages that need to be installed + + # Check if each package is already installed + foreach my $pkg (@pkgs) { + my $is_installed = script_run("rpm -q $pkg"); # Returns 0 if installed, non-zero if not + if ($is_installed != 0) { + push @to_install, $pkg; # Add package to install list if not installed + } else { + record_info("Package already installed", "$pkg is already installed"); + } + } + + # Proceed only if there are packages to install + if (@to_install) { + my $pkg_list = join ' ', @to_install; # Convert @to_install to a space-separated string + if (is_transactional) { + assert_script_run "rebootmgrctl set-strategy instantly"; + record_info("Installing packages", "Using transactional-update, requires reboot: $pkg_list"); + trup_call "reboot pkg install $pkg_list"; + process_reboot(expected_grub => 1); + select_serial_terminal; + } else { + record_info("Installing packages", "Using zypper for installation: $pkg_list"); + zypper_call "in $pkg_list"; + } + } else { + record_info("No installation needed", "All packages are already installed"); + } +} + +sub setup_kea_container { + my (%args) = @_; + $args{nic} //= "eth0"; + $args{container_name} //= "dhcpd-server"; + $args{registry} //= $dhcpd_server_image_registry; + $args{image_name} //= $dhcpd_server_image_name; + + record_info("SETUP_DHCPD_CONTAINER"); + + assert_script_run("podman pull $args{registry}$args{image_name}", timeout => 300); + + # Start assembling the command as an AoA (Array of Arrays) + my @podman_cmd = ( + ["podman", "run", "-itd", "--net=host", "--privileged"], + ["-v", "/etc/kea:/etc/kea"], + $override_dns_string ne "" ? ["-e", "OVERRIDE_DNS=$override_dns_string"] : (), + ["--name", $args{container_name}], + [$args{image_name}], + ["kea-dhcp4", "-c", "/etc/kea/kea-dhcp4.conf"] + ); + + # Filter out any empty arrays + @podman_cmd = grep { @$_ } @podman_cmd; + + # Flatten AoA into a single string command + my $podman_cmd_string = join(" ", map { join(" ", @$_) } @podman_cmd); + + # Run the container with the complete command + assert_script_run($podman_cmd_string, timeout => 120); + + # Verify the container is running + validate_script_output('podman ps', sub { m/$args{container_name}/ }); + validate_script_output('ss -lnp', sub { /$server_ip:67/ }); +} + +sub setup_server_network { + my ($nics_ref) = @_; + my @nics = @$nics_ref; + my $nic0 = $nics[0]; + + record_info("SETUP_SERVER_NETWORK"); + + # Bring down all non-loopback interfaces except the first one + foreach my $nic (@nics[1 .. $#nics]) { + record_info("Non-loopback interface $nic detected, bringing it down..."); + assert_script_run("ip link set $nic down"); + } + + delete_all_existing_connections(); + + my $netmask = cidr_to_netmask($subnet); + + if (is_nm_used()) { + # Setting IP and bringing the connection up + assert_script_run "nmcli con add type ethernet ifname $nic0 con-name $nic0"; + assert_script_run "nmcli con modify $nic0 ipv4.addresses ${server_ip}${subnet} ipv4.gateway $gateway ipv4.routes '0.0.0.0/0 $gateway' ipv4.method manual"; + assert_script_run "nmcli con modify $nic0 connection.autoconnect yes"; + assert_script_run "nmcli con up $nic0"; + } + + if (is_wicked_used()) { + # Wicked configuration: setting IP and Netmask + assert_script_run "echo 'BOOTPROTO=static' > /etc/sysconfig/network/ifcfg-$nic0"; + assert_script_run "echo 'STARTMODE=auto' >> /etc/sysconfig/network/ifcfg-$nic0"; + assert_script_run "echo 'IPADDR=$server_ip' >> /etc/sysconfig/network/ifcfg-$nic0"; + assert_script_run "echo 'GATEWAY=$gateway' >> /etc/sysconfig/network/ifcfg-$nic0"; + assert_script_run "echo 'NETMASK=$netmask' >> /etc/sysconfig/network/ifcfg-$nic0"; + + my $route_config_file = "/etc/sysconfig/network/ifroute-$nic0"; + + # Delete existing route configuration if it exists + script_run "rm -f $route_config_file"; + + # Create a new route configuration file + assert_script_run "echo 'default $gateway dev $nic0' > $route_config_file"; + + systemctl 'restart wicked'; + } +} + +sub configure_kea { + my (%args) = @_; + $args{nic} //= "eth0"; + assert_script_run("mkdir -p /etc/kea/ || true"); + assert_script_run("curl -v -o /etc/kea/kea-dhcp4.conf " . data_url("network_bonding/kea-dhcp4.conf")); + assert_script_run("sed -i \"s/eth0/$args{nic}/g\" /etc/kea/kea-dhcp4.conf"); +} + +sub configure_dnsmasq { + my (%args) = @_; + $args{nic} //= "eth0"; + assert_script_run("curl -v -o /etc/dnsmasq.conf " . data_url("network_bonding/dnsmasq.conf")); + assert_script_run("sed -i \"s/eth0/$args{nic}/g\" /etc/dnsmasq.conf"); + + # Remove existing server lines from /etc/dnsmasq.conf + assert_script_run('sed -i "/^server=/d" /etc/dnsmasq.conf'); + + foreach my $server (@container_primary_dns) { + record_info("DNS", "Adding general DNS server: $server"); + assert_script_run("echo 'server=$server' >> /etc/dnsmasq.conf"); + } + + my @domain_records = split(';', $override_dns_string); + + foreach my $record (@domain_records) { + my ($domain, $dns_ips) = split(':', $record); + my @ips = split(',', $dns_ips); + + foreach my $ip (@ips) { + record_info("DNS Override", "Adding custom DNS entry for $domain: $ip"); + assert_script_run("echo 'server=/$domain/$ip' >> /etc/dnsmasq.conf"); + } + } +} + +sub setup_server { + my ($self, $nics_ref) = @_; + my @nics = @$nics_ref; + my $nic0 = $nics[0]; + my @local_ns = ($server_ip); + + record_info("SETUP_SERVER"); + setup_server_network(\@nics); + + set_resolv(nameservers => \@public_dns); + + set_resolv(nameservers => \@scc_dns) if scalar(@scc_dns) > 0; + runtime_registration() if $requires_scc_registration; + + install_pkgs($self, "podman", "ethtool", "dnsmasq"); + + set_nics_link_speed_duplex({ + nics => \@nics, + speed => 1000, # assuming a speed of 1000 Mbps + duplex => 'full', # assuming full duplex + autoneg => 'off' # assuming autoneg is off + }); + + configure_dnsmasq(nic => $nic0); + assert_script_run("dnsmasq"); + validate_script_output('ps aux | grep [d]nsmasq', sub { /dnsmasq/ }); + validate_script_output('ss -uln', sub { /$server_ip:53/ }); + validate_script_output('ss -tln', sub { /$server_ip:53/ }); + + configure_kea(nic => $nic0); + set_resolv(nameservers => \@registry_dns) if scalar(@registry_dns) > 0; + setup_kea_container(); + + set_resolv(nameservers => \@local_ns); + check_connectivity_to_host_with_retry($nic0, "conncheck.opensuse.org"); + + barrier_wait "SERVER_SETUP_DONE"; + barrier_wait "BONDING_TESTS_DONE"; +} + +sub setup_client_network { + my ($nics_ref) = @_; + my @nics = @$nics_ref; + my $nic0 = $nics[0]; + my $timeout = 180; # Timeout in seconds + my $retry_interval = 10; # Interval between retries in seconds + + record_info("SETUP_CLIENT_NETWORK"); + delete_all_existing_connections(); + set_nic_dhcp_auto($nic0); + reload_connections_until_all_ips_assigned(nics => [$nic0]); + check_connectivity_to_host_with_retry($nic0, "conncheck.opensuse.org"); +} + +sub setup_client { + my ($self, $nics_ref) = @_; + my @nics = @$nics_ref; + + record_info("SETUP_CLIENT"); + setup_client_network(\@nics); + + runtime_registration() if $requires_scc_registration; + install_pkgs($self, "ethtool"); +} + +sub run { + my ($self) = @_; + my $hostname = get_var('HOSTNAME'); + my $is_server = ($hostname =~ /target/); + + if ($is_server) { + barrier_create "SERVER_SETUP_DONE", 2; + barrier_create "BONDING_TESTS_DONE", 2; + mutex_create 'barrier_setup_mm_done'; + } + mutex_wait 'barrier_setup_mm_done'; + + select_serial_terminal; + + my @nics = get_nics([]); + + assert_script_run("echo \"$server_ip server master\" >> /etc/hosts"); + + disable_and_stop_service($self->firewall, ignore_failure => 1) if check_unit_file($self->firewall); + disable_and_stop_service("apparmor", ignore_failure => 1) if check_unit_file("apparmor"); + + setup_server($self, \@nics) if $is_server; + + unless ($is_server) { + barrier_wait "SERVER_SETUP_DONE"; + setup_client($self, \@nics); + } +} + +1;