Skip to content

Commit

Permalink
Universal network bonding test
Browse files Browse the repository at this point in the history
  • Loading branch information
volodymyrkatkalov committed Sep 3, 2024
1 parent 3693928 commit 61be745
Show file tree
Hide file tree
Showing 3 changed files with 499 additions and 0 deletions.
15 changes: 15 additions & 0 deletions schedule/functional/extra_tests_network_bonding.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: extra_tests_network_bonding
description: >
Maintainer: vkatkalov.
Extra bonding tests
conditional_schedule:
bonding:
HOSTNAME:
'client':
- network/network_bonding
'server':
schedule:
- boot/boot_to_desktop
- installation/bootloader_start
- network/network_bonding_setup
- '{{bonding}}'

Check failure on line 15 in schedule/functional/extra_tests_network_bonding.yaml

View workflow job for this annotation

GitHub Actions / CI: Running static tests with perl v5.32

15:20 [new-line-at-end-of-file] no new line character at the end of file
368 changes: 368 additions & 0 deletions tests/network/network_bonding.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,368 @@
# SUSE's openQA tests
#
# Copyright 2016-2023 SUSE LLC
# SPDX-License-Identifier: FSFAP

# Summary: Test network bonding capability and connectivity
# Maintainer: QE Core <[email protected]>

use base "consoletest";
use strict;
use warnings;
use testapi;
use power_action_utils "power_action";
use utils qw(validate_script_output_retry);
use serial_terminal qw(select_serial_terminal);
use lockapi;
use utils;
use console::ovs_utils;
use version_utils;

my $BARRIER_BONDING_TESTS_DONE = 'BONDING_TESTS_DONE';

my $server_ip = "10.0.2.101";
my $client_ip = "10.0.2.102";
my $subnet = "/24";

my $use_nm;

sub cidr_to_netmask {
my ($cidr_str) = @_;

# Extract the numeric part from the string (e.g., "/32" -> 32)
$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);
}

sub set_nics_link_speed_duplex {
my ($nics_ref) = @_;
my @nics = @$nics_ref;

for my $nic (@nics) {
record_info("SET $nic link speed and duplex", "ethtool -s $nic speed 1000 duplex full autoneg off");
assert_script_run("ethtool -s $nic speed 1000 duplex full autoneg off");
}
}

sub is_nm_used {
return script_run("systemctl is-active NetworkManager") == 0;
}

sub is_wicked_used {
return script_run("systemctl is-active wicked") == 0;
}

sub check_connectivity {
my ($bond_name) = @_;
my $ping_host = $server_ip;
my $ping_command = "ping -c1 -I $bond_name $ping_host";

validate_script_output_retry(
$ping_command,
sub { m/1 packets transmitted, 1 received, 0% packet loss,/ }
);
}

sub get_nics_nm {
my ($bond_name) = @_;
my @devices;

# Use nmcli to get the list of network interfaces and their states
my @nmcli_output = split(/\n/, script_output('nmcli -f DEVICE,STATE device'));

foreach my $line (@nmcli_output) {
# Skip the header line
next if $line =~ /^DEVICE\s+STATE$/;

# Split each line into device and state
my ($device, $state) = split(/\s+/, $line, 2);

# Skip if the device is 'connected (externally)' or is the loopback or bond interface
next if ($state =~ /^connected \(externally\)/ || $device eq 'lo' || $device eq $bond_name);

# Include the device if it's 'connected' or any 'connecting' state (e.g., 'connecting (getting IP configuration)')
push @devices, $device if ($state =~ /^connected\b/ || $state =~ /^connecting\b/);
}

return @devices;
}

sub get_nics_wicked {
my ($bond_name) = @_;
my @devices;
my $within_interface_block = 0;

# Use wicked show-xml to get the list of network interfaces
my @xml_output = split(/\n/, script_output('wicked show-xml'));

foreach my $line (@xml_output) {
# Check if we are entering an <interface> block
if ($line =~ /<interface>/) {
$within_interface_block = 1;
}

# Check if we are exiting an <interface> block
if ($line =~ /<\/interface>/) {
$within_interface_block = 0;
}

# If inside an <interface> block and we find a <name> tag, capture the interface name
if ($within_interface_block && $line =~ /<name>([^<]+)<\/name>/) {
$within_interface_block = 0;
my $device = $1;
# Skip the loopback device 'lo' and any bond interface
next if $device eq 'lo';
next if $device eq $bond_name;
# Add the device to the list
push @devices, $device;
# We found the <name> within this block, no need to look further in this block
}
}

return @devices;
}

sub get_nics {
my ($bond_name) = @_;

if ($use_nm) {
return get_nics_nm($bond_name);
} else {
return get_nics_wicked($bond_name);
}
}

sub delete_existing_connections_nm {
my $output = script_output('nmcli -g DEVICE,UUID conn show');
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;
script_run "nmcli con delete uuid '$uuid'";
}
}

sub delete_existing_connections_wicked {
script_run "wicked ifdown all";
script_run "rm -f /etc/sysconfig/network/ifcfg-*";
}

sub delete_existing_connections {
if ($use_nm) {
delete_existing_connections_nm();
} else {
delete_existing_connections_wicked();
}
}

sub create_bond_nm {
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 create_and_configure_bond_wicked {
my ($bond_name, $bond_mode, $miimon, $ip_addr, $subnet, $gateway, @devices) = @_;

# Remove the old configuration file if it exists
script_run "rm -f /etc/sysconfig/network/ifcfg-$bond_name";

# Assuming $subnet holds the CIDR notation like '/24'
my $ifcfg_subnet = cidr_to_netmask $subnet;

# Create the new configuration file
script_run "echo 'BOOTPROTO=static' > /etc/sysconfig/network/ifcfg-$bond_name";
script_run "echo 'STARTMODE=auto' >> /etc/sysconfig/network/ifcfg-$bond_name";
script_run "echo 'BONDING_MASTER=yes' >> /etc/sysconfig/network/ifcfg-$bond_name";
script_run "echo 'BONDING_SLAVE=no' >> /etc/sysconfig/network/ifcfg-$bond_name";
script_run "echo 'BONDING_MODULE_OPTS=\"mode=$bond_mode miimon=$miimon\"' >> /etc/sysconfig/network/ifcfg-$bond_name";
script_run "echo 'IPADDR=$ip_addr' >> /etc/sysconfig/network/ifcfg-$bond_name";
script_run "echo 'NETMASK=$ifcfg_subnet' >> /etc/sysconfig/network/ifcfg-$bond_name";
script_run "echo 'GATEWAY=$gateway' >> /etc/sysconfig/network/ifcfg-$bond_name";

# Append slave interfaces to the bond configuration
my $index = 1;
foreach my $device (@devices) {
script_run "echo 'BONDING_SLAVE_$index=$device' >> /etc/sysconfig/network/ifcfg-$bond_name";
$index++;
}
}

sub configure_device_route_wicked {
my ($bond_name, $gateway) = @_;
my $route_config_file = "/etc/sysconfig/network/ifroute-$bond_name";

# Delete existing route configuration if it exists
script_run "rm -f $route_config_file";

# Create a new route configuration file
script_run "echo 'default $gateway dev $bond_name' > $route_config_file";
}

sub configure_device_route {
my ($bond_name, $gateway) = @_;
if ($use_nm) {
} else {
configure_device_route_wicked($bond_name, $gateway);
}
}

sub create_bond {
my ($bond_name, $bond_mode, $miimon, @nics) = @_;
if ($use_nm) {
create_bond_nm($bond_name, $bond_mode, $miimon);
} else {
create_and_configure_bond_wicked($bond_name, $bond_mode, $miimon, $client_ip, $subnet, $server_ip, @nics);
}
}

sub add_devices_to_bond_nm {
my ($bond_name, @devices) = @_;
foreach my $device (@devices) {
assert_script_run "nmcli con add type ethernet ifname $device master $bond_name";
}
}

sub add_devices_to_bond_wicked {
my ($bond_name, @devices) = @_;

foreach my $device (@devices) {
# Remove the old configuration file for the device if it exists
script_run "rm -f /etc/sysconfig/network/ifcfg-$device";

# Create the new configuration file for the device
script_run "echo 'BOOTPROTO=static' > /etc/sysconfig/network/ifcfg-$device";
script_run "echo 'STARTMODE=auto' >> /etc/sysconfig/network/ifcfg-$device";
script_run "echo 'BONDING_MASTER=no' >> /etc/sysconfig/network/ifcfg-$device";
script_run "echo 'BONDING_SLAVE=yes' >> /etc/sysconfig/network/ifcfg-$device";
script_run "echo 'BONDING_MASTER_IF=$bond_name' >> /etc/sysconfig/network/ifcfg-$device";
}
}

sub add_devices_to_bond {
my ($bond_name, @devices) = @_;
if ($use_nm) {
add_devices_to_bond_nm($bond_name, @devices);
} else {
add_devices_to_bond_wicked($bond_name, @devices);
}
}

sub configure_client_ip {
my ($bond_name) = @_;
if ($use_nm) {
assert_script_run "nmcli con modify $bond_name ipv4.addresses ${client_ip}${subnet}";
assert_script_run "nmcli con modify $bond_name ipv4.gateway $server_ip";
assert_script_run "nmcli con modify $bond_name ipv4.method manual";
assert_script_run "nmcli con up $bond_name";
} else {
# No need to handle wicked configuration here, as it's done in create_and_configure_bond_wicked
systemctl 'restart apparmor';
}
}

sub test_failover {
my ($bond_mode, $bond_name, $device, $description, $nics_ref) = @_;
my @nics_status = map { [$_, $_ eq $device ? 0 : 1] } @$nics_ref;

record_info("Testing Failover for Mode: $bond_mode", "NIC: $device");

assert_script_run "ip link set dev $device down";
script_run 'ip a';

# 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;

assert_script_run "ip link set dev $device up";
systemctl 'restart wicked' unless $use_nm;
}

sub validate_bond_mode_and_slaves {
my ($bond_name, $description, $devices_ref) = @_;

assert_script_run "cat /proc/net/bonding/$bond_name | grep 'Mode:' | grep '$description'";

foreach my $device_info (@$devices_ref) {
my ($device, $status_up) = @$device_info;
my $expected_status = $status_up ? 'up' : 'down';

validate_script_output_retry(
"grep -A 1 'Slave Interface: $device' /proc/net/bonding/$bond_name",
sub { m/MII Status: $expected_status/ }
);
}
}

sub test_bonding_mode {
my ($self, $nics_ref, $miimon, $bond_name, $bond_mode, $description) = @_;
my @nics = @$nics_ref;

select_serial_terminal;

delete_existing_connections;

create_bond($bond_name, $bond_mode, $miimon, @nics);
add_devices_to_bond($bond_name, @nics);
configure_device_route($bond_name, $server_ip);

configure_client_ip($bond_name);

power_action('reboot', textmode => 1);
$self->wait_boot;
select_serial_terminal;
set_nics_link_speed_duplex(\@nics);
check_connectivity $bond_name;

# Validate that all NICs are "up"
validate_bond_mode_and_slaves($bond_name, $description, [map { [$_, 1] } @nics]);

# Testing failover for each NIC
test_failover($bond_mode, $bond_name, $_, $description, \@nics) for @nics;

delete_existing_connections;
}

sub run {
my ($self) = @_;
select_serial_terminal;

$use_nm = is_nm_used();

my $bond_name = "bond0";
my $miimon = 200;
my @nics = get_nics($bond_name);

record_info(scalar(@nics) . " NICs Detected", join(', ', @nics));

my @bond_modes = (
['balance-rr', 'load balancing (round-robin)'],
['active-backup', 'fault-tolerance (active-backup)'],
['balance-xor', 'load balancing (xor)'],
['broadcast', 'fault-tolerance (broadcast)'],
['802.3ad', 'IEEE 802.3ad Dynamic link aggregation'],
['balance-tlb', 'transmit load balancing'],
['balance-alb', 'adaptive load balancing']
);

foreach my $mode_info (@bond_modes) {
my ($bond_mode, $description) = @$mode_info;
record_info("Testing Bonding Mode: $bond_mode", $description);
test_bonding_mode($self, \@nics, $miimon, $bond_name, $bond_mode, $description);
}

barrier_wait $BARRIER_BONDING_TESTS_DONE;
}

1;
Loading

0 comments on commit 61be745

Please sign in to comment.