Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Universal network bonding test #20116

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions data/network_bonding/dnsmasq.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
interface=eth0
bind-interfaces
no-resolv
cache-size=4096
server=8.8.8.8
28 changes: 28 additions & 0 deletions data/network_bonding/kea-dhcp4.conf
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
]
}
}
329 changes: 327 additions & 2 deletions lib/network_utils.pm
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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;
Loading
Loading