From 74075c94202791b6e9707955f59c1e4f382a396a Mon Sep 17 00:00:00 2001 From: Travis Holloway Date: Mon, 3 Jun 2024 15:14:56 -0500 Subject: [PATCH] Convert multiple NICs blocker to a component Case RE-337: Previously, we blocked if we found that there were multiple NICs with names conflicting with the kernel naming space. This change makes it so that we give a warning about this when the script is executed in check mode, and we rename the NICs to a different naming scheme if the script is executed in start mode. Of note, we do explicitely ask the user for permission to make this change on their behalf before performing the change since there is an inherint risk involved of networking not coming back up when the server reboots. Changelog: Convert multiple NICs blocker to component --- elevate-cpanel | 231 ++++++++++++++++++++++++++++++--- lib/Elevate/Blockers/Leapp.pm | 1 + lib/Elevate/Blockers/NICs.pm | 93 ++++++++++--- lib/Elevate/Components/NICs.pm | 99 ++++++++++++++ lib/Elevate/Constants.pm | 4 + lib/Elevate/NICs.pm | 41 ++++++ script/elevate-cpanel.PL | 3 + t/blocker-NICs.t | 42 ++++-- t/components-NICs.t | 135 +++++++++++++++++++ t/elevate-nics.t | 101 ++++++++++++++ 10 files changed, 698 insertions(+), 52 deletions(-) create mode 100644 lib/Elevate/Components/NICs.pm create mode 100644 lib/Elevate/NICs.pm create mode 100644 t/components-NICs.t create mode 100644 t/elevate-nics.t diff --git a/elevate-cpanel b/elevate-cpanel index 56a8c6dc..616b0c1f 100755 --- a/elevate-cpanel +++ b/elevate-cpanel @@ -50,6 +50,7 @@ BEGIN { # Suppress load of all of these at earliest point. $INC{'Elevate/Components/KernelCare.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/Kernel.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/MySQL.pm'} = 'script/elevate-cpanel.PL.static'; + $INC{'Elevate/Components/NICs.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/NixStats.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/PECL.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/PerlXS.pm'} = 'script/elevate-cpanel.PL.static'; @@ -72,6 +73,7 @@ BEGIN { # Suppress load of all of these at earliest point. $INC{'Elevate/Logger.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Marker.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Motd.pm'} = 'script/elevate-cpanel.PL.static'; + $INC{'Elevate/NICs.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Notify.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Roles/Run.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/RPM.pm'} = 'script/elevate-cpanel.PL.static'; @@ -112,6 +114,10 @@ BEGIN { # Suppress load of all of these at earliest point. use constant IGNORE_OUTDATED_SERVICES_FILE => q[/etc/cpanel/local/ignore_outdated_services]; + use constant SBIN_IP => q[/sbin/ip]; + + use constant ETH_FILE_PREFIX => q[/etc/sysconfig/network-scripts/ifcfg-]; + 1; } # --- END lib/Elevate/Constants.pm @@ -1625,17 +1631,17 @@ EOS use cPstrict; use Elevate::Constants (); + use Elevate::NICs (); # use Elevate::Blockers::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Blockers::Base); } - use Cwd (); - # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } - use constant SBIN_IP => q[/sbin/ip]; + use constant ETH_FILE_PREFIX => Elevate::Constants::ETH_FILE_PREFIX; + use constant SBIN_IP => Elevate::Constants::SBIN_IP; sub check ($self) { return 1 unless $self->should_run_leapp; # skip when --no-leapp is provided @@ -1644,38 +1650,90 @@ EOS sub _blocker_bad_nics_naming ($self) { return $self->has_blocker( q[Missing ] . SBIN_IP . ' binary' ) unless -x SBIN_IP; - my @eths = _get_nics(); + my @eths = Elevate::NICs::get_nics(); if ( @eths >= 2 ) { - return $self->has_blocker( <<~'EOS'); - Your machine has multiple network interface cards (NICs) using kernel-names (ethX). - Since the upgrade process cannot guarantee their stability after upgrade, you cannot upgrade. + WARN( <<~'EOS' ); + Your machine has multiple network interface cards (NICs) using + kernel-names (ethX). + EOS + + if ( $self->is_check_mode() ) { + INFO( <<~'EOS' ); + Since the upgrade process cannot guarantee their stability after + upgrade, we will need to rename these interfaces before upgrading. + EOS + return 0; + } + + return if $self->_nics_have_missing_ifcfg_files(@eths); + + my $pretty_distro_name = $self->upgrade_to_pretty_name(); + WARN( <<~"EOS" ); + Prior to elevating this system to $pretty_distro_name, we will + automatically rename these interfaces. - Please provide those interfaces new names before continuing the update. EOS + + if ( !$self->getopt('non-interactive') ) { + if ( + !IO::Prompt::prompt( + '-one_char', + '-yes_no', + '-tty', + -default => 'y', + 'Do you consent to renaming your NICs to use non kernel-names [Y/n]: ', + ) + ) { + return $self->has_blocker( <<~"EOS" ); + The system cannot be elevated to $pretty_distro_name until the + NICs using kernel-names (ethX) have been updated with new names. + + Please provide those interfaces new names before continuing the + update. + + To have this script perform the upgrade, run this script again + and consent to allow it to rename the NICs. + EOS + } + } } return 0; } - sub _get_nics { - my $ip_info = Cpanel::SafeRun::Errors::saferunnoerror( SBIN_IP, 'addr' ) // ''; + sub _nics_have_missing_ifcfg_files ( $self, @nics ) { - my @eths; - foreach my $line ( split /\n/xms, $ip_info ) { - $line =~ /^[0-9]+: \s (eth[0-9]):/xms - or next; + my @nics_missing_nic_path; + foreach my $nic (@nics) { + my $nic_path = ETH_FILE_PREFIX . $nic; - my $eth = $1; - my $value = readlink "/sys/class/net/$eth" - or next; + my $err_msg = <<~"EOS"; + The file for the network interface card (NIC) using kernel-name ($nic) does + not exist at the expected location ($nic_path). We are unable to + change the name of this NIC due to this. You will need to resolve this + issue manually before elevate can continue. - $value =~ m{/virtual/}xms - and next; + EOS - push @eths, $eth; + unless ( -s $nic_path ) { + ERROR($err_msg); + push @nics_missing_nic_path, $nic; + } } - return @eths; + if (@nics_missing_nic_path) { + my $missing_nics = join "\n", @nics_missing_nic_path; + return $self->has_blocker( <<~"EOS" ); + This script is unable to rename the following network interface cards + due to a missing ifcfg file: + + $missing_nics + + Please provide these interfaces new names before continuing the update. + EOS + } + + return; } 1; @@ -2469,6 +2527,7 @@ EOS qw( check_installed_devel_kernels cl_mysql_repository_setup + persistentnetnamesdisable verify_check_results ) ); @@ -4382,6 +4441,97 @@ EOS } # --- END lib/Elevate/Components/MySQL.pm +{ # --- BEGIN lib/Elevate/Components/NICs.pm + + package Elevate::Components::NICs; + + use cPstrict; + + use File::Slurper (); + + use Elevate::Constants (); + use Elevate::NICs (); + + # use Log::Log4perl qw(:easy); + INIT { Log::Log4perl->import(qw{:easy}); } + + # use Elevate::Components::Base(); + our @ISA; + BEGIN { push @ISA, qw(Elevate::Components::Base); } + + use constant ETH_FILE_PREFIX => Elevate::Constants::ETH_FILE_PREFIX; + use constant NIC_PREFIX => q[cp]; + use constant PERSISTENT_NET_RULES_PATH => q[/etc/udev/rules.d/70-persistent-net.rules]; + + sub pre_leapp ($self) { + + $self->_rename_nics(); + + return; + } + + sub _rename_nics ($self) { + + my @nics = Elevate::NICs::get_nics(); + return unless scalar @nics > 1; + + foreach my $nic (@nics) { + my $nic_path = ETH_FILE_PREFIX . $nic; + + my $die_msg = <<~"EOS"; + The file for the network interface card (NIC) using kernel-name ($nic) does + not exist at the expected location ($nic_path). We are unable to + change the name of this NIC due to this. You will need to resolve this + issue manually before elevate can continue. Once the issue has been + resolved, you can continue this script by executing: + + /scripts/elevate-cpanel --continue + EOS + die "$die_msg\n" unless -s $nic_path; + + my $new_nic = NIC_PREFIX . $nic; + INFO("Renaming $nic to $new_nic"); + + my $device_line_found = 0; + my $txt = File::Slurper::read_binary($nic_path); + my @nic_lines = split( "\n", $txt ); + foreach my $line (@nic_lines) { + if ( $line =~ m{^\s*DEVICE\s*=\s*\Q$nic\E\s*$} ) { + $device_line_found = 1; + $line = "DEVICE=$new_nic"; + last; + } + } + die qq[Unable to rename $nic to $new_nic. The line beginning with 'DEVICE' in $nic_path was not found.\n] unless $device_line_found; + + my $new_nic_path = ETH_FILE_PREFIX . $new_nic; + File::Slurper::write_binary( $new_nic_path, join( "\n", @nic_lines ) ); + + unlink $nic_path; + + next unless -s PERSISTENT_NET_RULES_PATH; + my $rules_txt = File::Slurper::read_binary( PERSISTENT_NET_RULES_PATH() ); + my @rules_lines = split( "\n", $rules_txt ); + foreach my $line (@rules_lines) { + $line =~ s/NAME="\Q$nic\E"/NAME="$new_nic"/; + } + + my $new_rules_txt = join( "\n", @rules_lines ); + $new_rules_txt .= "\n"; + File::Slurper::write_binary( PERSISTENT_NET_RULES_PATH(), $new_rules_txt ); + } + + return; + } + + sub post_leapp ($self) { + return; + } + + 1; + +} # --- END lib/Elevate/Components/NICs.pm + { # --- BEGIN lib/Elevate/Components/NixStats.pm package Elevate::Components::NixStats; @@ -6459,6 +6609,42 @@ EOS } # --- END lib/Elevate/Motd.pm +{ # --- BEGIN lib/Elevate/NICs.pm + + package Elevate::NICs; + + use cPstrict; + + use Elevate::Constants (); + + use Cpanel::SafeRun::Errors (); + + sub get_nics () { + my $sbin_ip = Elevate::Constants::SBIN_IP(); + my $ip_info = Cpanel::SafeRun::Errors::saferunnoerror( $sbin_ip, 'addr' ) // ''; + + my @eths; + foreach my $line ( split /\n/xms, $ip_info ) { + $line =~ /^[0-9]+: \s (eth[0-9]):/xms + or next; + + my $eth = $1; + my $value = readlink "/sys/class/net/$eth" + or next; + + $value =~ m{/virtual/}xms + and next; + + push @eths, $eth; + } + + return @eths; + } + + 1; + +} # --- END lib/Elevate/NICs.pm + { # --- BEGIN lib/Elevate/Notify.pm package Elevate::Notify; @@ -7639,6 +7825,7 @@ use Elevate::Components::LiteSpeed (); use Elevate::Components::KernelCare (); use Elevate::Components::Kernel (); use Elevate::Components::MySQL (); +use Elevate::Components::NICs (); use Elevate::Components::NixStats (); use Elevate::Components::PECL (); use Elevate::Components::PerlXS (); @@ -7664,6 +7851,7 @@ use Elevate::Leapp (); use Elevate::Logger (); use Elevate::Marker (); use Elevate::Motd (); +use Elevate::NICs (); use Elevate::Notify (); use Elevate::Roles::Run (); # used as parent, but ensure fatpack use Elevate::RPM (); @@ -8333,6 +8521,7 @@ sub run_stage_2 ($self) { $self->run_component_once( 'AutoSSL' => 'pre_leapp' ); $self->run_component_once( 'KernelCare' => 'pre_leapp' ); $self->run_component_once( 'Grub2' => 'pre_leapp' ); + $self->run_component_once( 'NICs' => 'pre_leapp' ); return ACTION_REBOOT_NEEDED; } diff --git a/lib/Elevate/Blockers/Leapp.pm b/lib/Elevate/Blockers/Leapp.pm index 9f7013e3..5dd2832a 100644 --- a/lib/Elevate/Blockers/Leapp.pm +++ b/lib/Elevate/Blockers/Leapp.pm @@ -59,6 +59,7 @@ sub _check_for_inhibitors ($self) { qw( check_installed_devel_kernels cl_mysql_repository_setup + persistentnetnamesdisable verify_check_results ) ); diff --git a/lib/Elevate/Blockers/NICs.pm b/lib/Elevate/Blockers/NICs.pm index 8d7ce7e8..8a7fbbd9 100644 --- a/lib/Elevate/Blockers/NICs.pm +++ b/lib/Elevate/Blockers/NICs.pm @@ -13,13 +13,14 @@ Blocker to check if the server is using multiple NICs. use cPstrict; use Elevate::Constants (); +use Elevate::NICs (); use parent qw{Elevate::Blockers::Base}; -use Cwd (); use Log::Log4perl qw(:easy); -use constant SBIN_IP => q[/sbin/ip]; +use constant ETH_FILE_PREFIX => Elevate::Constants::ETH_FILE_PREFIX; +use constant SBIN_IP => Elevate::Constants::SBIN_IP; sub check ($self) { return 1 unless $self->should_run_leapp; # skip when --no-leapp is provided @@ -28,38 +29,90 @@ sub check ($self) { sub _blocker_bad_nics_naming ($self) { return $self->has_blocker( q[Missing ] . SBIN_IP . ' binary' ) unless -x SBIN_IP; - my @eths = _get_nics(); + my @eths = Elevate::NICs::get_nics(); if ( @eths >= 2 ) { - return $self->has_blocker( <<~'EOS'); - Your machine has multiple network interface cards (NICs) using kernel-names (ethX). - Since the upgrade process cannot guarantee their stability after upgrade, you cannot upgrade. + WARN( <<~'EOS' ); + Your machine has multiple network interface cards (NICs) using + kernel-names (ethX). + EOS + + if ( $self->is_check_mode() ) { + INFO( <<~'EOS' ); + Since the upgrade process cannot guarantee their stability after + upgrade, we will need to rename these interfaces before upgrading. + EOS + return 0; + } + + return if $self->_nics_have_missing_ifcfg_files(@eths); + + my $pretty_distro_name = $self->upgrade_to_pretty_name(); + WARN( <<~"EOS" ); + Prior to elevating this system to $pretty_distro_name, we will + automatically rename these interfaces. - Please provide those interfaces new names before continuing the update. EOS + + if ( !$self->getopt('non-interactive') ) { + if ( + !IO::Prompt::prompt( + '-one_char', + '-yes_no', + '-tty', + -default => 'y', + 'Do you consent to renaming your NICs to use non kernel-names [Y/n]: ', + ) + ) { + return $self->has_blocker( <<~"EOS" ); + The system cannot be elevated to $pretty_distro_name until the + NICs using kernel-names (ethX) have been updated with new names. + + Please provide those interfaces new names before continuing the + update. + + To have this script perform the upgrade, run this script again + and consent to allow it to rename the NICs. + EOS + } + } } return 0; } -sub _get_nics { - my $ip_info = Cpanel::SafeRun::Errors::saferunnoerror( SBIN_IP, 'addr' ) // ''; +sub _nics_have_missing_ifcfg_files ( $self, @nics ) { - my @eths; - foreach my $line ( split /\n/xms, $ip_info ) { - $line =~ /^[0-9]+: \s (eth[0-9]):/xms - or next; + my @nics_missing_nic_path; + foreach my $nic (@nics) { + my $nic_path = ETH_FILE_PREFIX . $nic; - my $eth = $1; - my $value = readlink "/sys/class/net/$eth" - or next; + my $err_msg = <<~"EOS"; + The file for the network interface card (NIC) using kernel-name ($nic) does + not exist at the expected location ($nic_path). We are unable to + change the name of this NIC due to this. You will need to resolve this + issue manually before elevate can continue. - $value =~ m{/virtual/}xms - and next; + EOS - push @eths, $eth; + unless ( -s $nic_path ) { + ERROR($err_msg); + push @nics_missing_nic_path, $nic; + } + } + + if (@nics_missing_nic_path) { + my $missing_nics = join "\n", @nics_missing_nic_path; + return $self->has_blocker( <<~"EOS" ); + This script is unable to rename the following network interface cards + due to a missing ifcfg file: + + $missing_nics + + Please provide these interfaces new names before continuing the update. + EOS } - return @eths; + return; } 1; diff --git a/lib/Elevate/Components/NICs.pm b/lib/Elevate/Components/NICs.pm new file mode 100644 index 00000000..5515101d --- /dev/null +++ b/lib/Elevate/Components/NICs.pm @@ -0,0 +1,99 @@ +package Elevate::Components::NICs; + +=encoding utf-8 + +=head1 NAME + +Elevate::Components::NICs + +Rename NICs in the kernel namespace from ethX to cpethX + +=cut + +use cPstrict; + +use File::Slurper (); + +use Elevate::Constants (); +use Elevate::NICs (); + +use Log::Log4perl qw(:easy); + +use parent qw{Elevate::Components::Base}; + +use constant ETH_FILE_PREFIX => Elevate::Constants::ETH_FILE_PREFIX; +use constant NIC_PREFIX => q[cp]; +use constant PERSISTENT_NET_RULES_PATH => q[/etc/udev/rules.d/70-persistent-net.rules]; + +sub pre_leapp ($self) { + + $self->_rename_nics(); + + return; +} + +sub _rename_nics ($self) { + + # Only do this if there are multiple NICs in the kernel (eth) namespace + my @nics = Elevate::NICs::get_nics(); + return unless scalar @nics > 1; + + foreach my $nic (@nics) { + my $nic_path = ETH_FILE_PREFIX . $nic; + + my $die_msg = <<~"EOS"; + The file for the network interface card (NIC) using kernel-name ($nic) does + not exist at the expected location ($nic_path). We are unable to + change the name of this NIC due to this. You will need to resolve this + issue manually before elevate can continue. Once the issue has been + resolved, you can continue this script by executing: + + /scripts/elevate-cpanel --continue + EOS + die "$die_msg\n" unless -s $nic_path; + + my $new_nic = NIC_PREFIX . $nic; + INFO("Renaming $nic to $new_nic"); + + # Update the name of the NIC in the network config file + my $device_line_found = 0; + my $txt = File::Slurper::read_binary($nic_path); + my @nic_lines = split( "\n", $txt ); + foreach my $line (@nic_lines) { + if ( $line =~ m{^\s*DEVICE\s*=\s*\Q$nic\E\s*$} ) { + $device_line_found = 1; + $line = "DEVICE=$new_nic"; + last; + } + } + die qq[Unable to rename $nic to $new_nic. The line beginning with 'DEVICE' in $nic_path was not found.\n] unless $device_line_found; + + my $new_nic_path = ETH_FILE_PREFIX . $new_nic; + File::Slurper::write_binary( $new_nic_path, join( "\n", @nic_lines ) ); + + unlink $nic_path; + + # If this file exists, then it will be read on reboot and the network + # config files will be expected to match. Most virtual servers will + # have this file and networking will not come back up on reboot if the + # values in this file do not match the values in the network config files + next unless -s PERSISTENT_NET_RULES_PATH; + my $rules_txt = File::Slurper::read_binary( PERSISTENT_NET_RULES_PATH() ); + my @rules_lines = split( "\n", $rules_txt ); + foreach my $line (@rules_lines) { + $line =~ s/NAME="\Q$nic\E"/NAME="$new_nic"/; + } + + my $new_rules_txt = join( "\n", @rules_lines ); + $new_rules_txt .= "\n"; + File::Slurper::write_binary( PERSISTENT_NET_RULES_PATH(), $new_rules_txt ); + } + + return; +} + +sub post_leapp ($self) { + return; +} + +1; diff --git a/lib/Elevate/Constants.pm b/lib/Elevate/Constants.pm index 4ef2e52a..d6496664 100644 --- a/lib/Elevate/Constants.pm +++ b/lib/Elevate/Constants.pm @@ -37,4 +37,8 @@ use constant CHKSRVD_SUSPEND_FILE => q[/var/run/chkservd.suspend]; use constant IGNORE_OUTDATED_SERVICES_FILE => q[/etc/cpanel/local/ignore_outdated_services]; +use constant SBIN_IP => q[/sbin/ip]; + +use constant ETH_FILE_PREFIX => q[/etc/sysconfig/network-scripts/ifcfg-]; + 1; diff --git a/lib/Elevate/NICs.pm b/lib/Elevate/NICs.pm new file mode 100644 index 00000000..006bc198 --- /dev/null +++ b/lib/Elevate/NICs.pm @@ -0,0 +1,41 @@ +package Elevate::NICs; + +=encoding utf-8 + +=head1 NAME + +Elevate::NICs + +Helper/Utility logic for NIC related tasks. + +=cut + +use cPstrict; + +use Elevate::Constants (); + +use Cpanel::SafeRun::Errors (); + +sub get_nics () { + my $sbin_ip = Elevate::Constants::SBIN_IP(); + my $ip_info = Cpanel::SafeRun::Errors::saferunnoerror( $sbin_ip, 'addr' ) // ''; + + my @eths; + foreach my $line ( split /\n/xms, $ip_info ) { + $line =~ /^[0-9]+: \s (eth[0-9]):/xms + or next; + + my $eth = $1; + my $value = readlink "/sys/class/net/$eth" + or next; + + $value =~ m{/virtual/}xms + and next; + + push @eths, $eth; + } + + return @eths; +} + +1; diff --git a/script/elevate-cpanel.PL b/script/elevate-cpanel.PL index 46bc412a..2e4c703a 100755 --- a/script/elevate-cpanel.PL +++ b/script/elevate-cpanel.PL @@ -270,6 +270,7 @@ use Elevate::Components::LiteSpeed (); use Elevate::Components::KernelCare (); use Elevate::Components::Kernel (); use Elevate::Components::MySQL (); +use Elevate::Components::NICs (); use Elevate::Components::NixStats (); use Elevate::Components::PECL (); use Elevate::Components::PerlXS (); @@ -295,6 +296,7 @@ use Elevate::Leapp (); use Elevate::Logger (); use Elevate::Marker (); use Elevate::Motd (); +use Elevate::NICs (); use Elevate::Notify (); use Elevate::Roles::Run (); # used as parent, but ensure fatpack use Elevate::RPM (); @@ -964,6 +966,7 @@ sub run_stage_2 ($self) { $self->run_component_once( 'AutoSSL' => 'pre_leapp' ); $self->run_component_once( 'KernelCare' => 'pre_leapp' ); $self->run_component_once( 'Grub2' => 'pre_leapp' ); + $self->run_component_once( 'NICs' => 'pre_leapp' ); return ACTION_REBOOT_NEEDED; } diff --git a/t/blocker-NICs.t b/t/blocker-NICs.t index 865aba32..5b16b509 100644 --- a/t/blocker-NICs.t +++ b/t/blocker-NICs.t @@ -23,11 +23,17 @@ use Test::Elevate; use cPstrict; my $cpev_mock = Test::MockModule->new('cpev'); -my $nics_mock = Test::MockModule->new('Elevate::Blockers::NICs'); +my $nics_mock = Test::MockModule->new('Elevate::NICs'); my $cpev = cpev->new; my $nics = $cpev->get_blocker('NICs'); +my $user_consent = 0; +my $mock_io_prompt = Test::MockModule->new('IO::Prompt'); +$mock_io_prompt->redefine( + prompt => sub { return $user_consent; }, +); + ## Make sure we have NICs that would fail #my $mock_ip_addr = q{1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 # link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 @@ -57,7 +63,9 @@ my $nics = $cpev->get_blocker('NICs'); # The NICs blocker runs /sbin/ip which breaks because Cpanel::SafeRun::Simple # opens /dev/null which Test::MockFile does not mock and is annoyed by it - my $sbin_ip = Test::MockFile->file('/sbin/ip'); + my $sbin_ip = Test::MockFile->file('/sbin/ip'); + my $ifcfg_eth0 = Test::MockFile->file( '/etc/sysconfig/network-scripts/ifcfg-eth0', 'mocked' ); + my $ifcfg_eth1 = Test::MockFile->file( '/etc/sysconfig/network-scripts/ifcfg-eth1', 'mocked' ); note "checking kernel-named NICs"; # what happens if /sbin/ip is not available @@ -76,22 +84,24 @@ my $nics = $cpev->get_blocker('NICs'); $sbin_ip->contents(''); chmod 755, $sbin_ip->path(); - $nics_mock->redefine( '_get_nics' => sub { qw< eth0 eth1 > } ); - is( + $nics_mock->redefine( 'get_nics' => sub { qw< eth0 eth1 > } ); + like( $nics->_blocker_bad_nics_naming(), { id => q[Elevate::Blockers::NICs::_blocker_bad_nics_naming], - msg => <<~'EOS', - Your machine has multiple network interface cards (NICs) using kernel-names (ethX). - Since the upgrade process cannot guarantee their stability after upgrade, you cannot upgrade. - - Please provide those interfaces new names before continuing the update. - EOS + msg => qr/To have this script perform the upgrade/, }, q{What happens when ip addr returns eth0 and eth1} ); - $nics_mock->redefine( '_get_nics' => sub { qw< w0p1lan > } ); + $user_consent = 1; + is( + $nics->_blocker_bad_nics_naming(), + 0, + 'No blocker when the user consents to the script renaming the NICs' + ); + + $nics_mock->redefine( 'get_nics' => sub { qw< w0p1lan > } ); $errors_mock->redefine( 'saferunnoerror' => sub { $_[0] eq '/sbin/ip' ? '' : $errors_mock->original('saferunnoerror'); @@ -99,6 +109,16 @@ my $nics = $cpev->get_blocker('NICs'); ); is( $nics->_blocker_bad_nics_naming(), 0, "No blocker with w0p1lan ethernet card" ); + + unlink '/etc/sysconfig/network-scripts/ifcfg-eth1'; + like( + $nics->_nics_have_missing_ifcfg_files( 'eth0', 'eth1' ), + { + id => q[Elevate::Blockers::NICs::_nics_have_missing_ifcfg_files], + msg => qr/This script is unable to rename the following network interface cards\ndue to a missing ifcfg file/, + }, + 'Blocker when the ifcfg does not exist' + ); } done_testing(); diff --git a/t/components-NICs.t b/t/components-NICs.t new file mode 100644 index 00000000..01548090 --- /dev/null +++ b/t/components-NICs.t @@ -0,0 +1,135 @@ +#!/usr/local/cpanel/3rdparty/bin/perl + +# Copyright 2024 WebPros International, LLC +# All rights reserved. +# copyright@cpanel.net http://cpanel.net +# This code is subject to the cPanel license. Unauthorized copying is prohibited. + +package test::cpev::components; + +use FindBin; + +use Test2::V0; +use Test2::Tools::Explain; +use Test2::Plugin::NoWarnings; +use Test2::Tools::Exception; + +use Test::MockFile 0.032; +use Test::MockModule qw/strict/; + +use lib $FindBin::Bin . "/lib"; +use Test::Elevate; + +use cPstrict; + +my $nics = bless {}, 'Elevate::Components::NICs'; + +{ + note "checking pre_leapp"; + + my $mock_nics = Test::MockModule->new('Elevate::NICs'); + $mock_nics->redefine( + get_nics => sub { return ('eth0'); }, + ); + + is( $nics->_rename_nics(), undef, '_rename_nics is a noop when there are not multiple nics' ); + + $mock_nics->redefine( + get_nics => sub { return ( 'eth0', 'eth1', 'eth2' ); }, + ); + + my $mock_persistent_rules_path = Test::MockFile->file('/etc/udev/rules.d/70-persistent-net.rules'); + + my @mocked_old_ifcfg_files; + my @mocked_new_ifcfg_files; + for my $i ( 0 .. 2 ) { + my $mock_ifcfg_old = Test::MockFile->file("/etc/sysconfig/network-scripts/ifcfg-eth$i"); + push @mocked_old_ifcfg_files, $mock_ifcfg_old; + + my $mock_ifcfg_new = Test::MockFile->file("/etc/sysconfig/network-scripts/ifcfg-cpeth$i"); + push @mocked_new_ifcfg_files, $mock_ifcfg_new; + } + + like( + dies { $nics->_rename_nics() }, + qr/The file for the network interface card \(NIC\) using kernel-name \(eth0\) does\nnot exist/, + 'Dies when the expected network config file does not exist' + ); + + @mocked_old_ifcfg_files = (); + @mocked_new_ifcfg_files = (); + for my $i ( 0 .. 2 ) { + my $mock_ifcfg_old = Test::MockFile->file( "/etc/sysconfig/network-scripts/ifcfg-eth$i", "this\nis\nnot\nvalid\nsyntax" ); + push @mocked_old_ifcfg_files, $mock_ifcfg_old; + + my $mock_ifcfg_new = Test::MockFile->file("/etc/sysconfig/network-scripts/ifcfg-cpeth$i"); + push @mocked_new_ifcfg_files, $mock_ifcfg_new; + } + + like( + dies { $nics->_rename_nics() }, + qr/Unable to rename eth0 to cpeth0/, + 'Dies when the ifcfg file does not contains the expected line' + ); + message_seen( 'INFO', "Renaming eth0 to cpeth0" ); + + @mocked_old_ifcfg_files = (); + @mocked_new_ifcfg_files = (); + for my $i ( 0 .. 2 ) { + my $mock_ifcfg_old = Test::MockFile->file( "/etc/sysconfig/network-scripts/ifcfg-eth$i", "DEVICE=eth$i" ); + push @mocked_old_ifcfg_files, $mock_ifcfg_old; + + my $mock_ifcfg_new = Test::MockFile->file("/etc/sysconfig/network-scripts/ifcfg-cpeth$i"); + push @mocked_new_ifcfg_files, $mock_ifcfg_new; + } + + $nics->_rename_nics(); + + for my $i ( 0 .. 2 ) { + + is( + $mocked_new_ifcfg_files[$i]->contents(), + "DEVICE=cpeth$i", + 'The new ifcfg file contains the expected contents' + ); + + message_seen( 'INFO', "Renaming eth$i to cpeth$i" ); + } + + @mocked_old_ifcfg_files = (); + @mocked_new_ifcfg_files = (); + for my $i ( 0 .. 2 ) { + my $mock_ifcfg_old = Test::MockFile->file( "/etc/sysconfig/network-scripts/ifcfg-eth$i", "DEVICE=eth$i" ); + push @mocked_old_ifcfg_files, $mock_ifcfg_old; + + my $mock_ifcfg_new = Test::MockFile->file("/etc/sysconfig/network-scripts/ifcfg-cpeth$i"); + push @mocked_new_ifcfg_files, $mock_ifcfg_new; + } + + open( my $w_fh, '>', '/etc/udev/rules.d/70-persistent-net.rules' ); + print $w_fh q[SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="fa:16:3e:48:13:b7", NAME="eth0"\nSUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="fa:16:3e:48:13:42", NAME="eth1"\n]; + close $w_fh; + + $nics->_rename_nics(); + + for my $i ( 0 .. 2 ) { + + is( + $mocked_new_ifcfg_files[$i]->contents(), + "DEVICE=cpeth$i", + 'The new ifcfg file contains the expected contents' + ); + + message_seen( 'INFO', "Renaming eth$i to cpeth$i" ); + } + + like( + $mock_persistent_rules_path->contents(), + qr/NAME="cpeth0".*NAME="cpeth1"/, + '70-persistent-net.rules gets updated when it exists' + ); + + no_messages_seen(); +} + +done_testing(); diff --git a/t/elevate-nics.t b/t/elevate-nics.t new file mode 100644 index 00000000..a98e1104 --- /dev/null +++ b/t/elevate-nics.t @@ -0,0 +1,101 @@ +#!/usr/local/cpanel/3rdparty/bin/perl + +# Copyright 2024 WebPros International, LLC +# All rights reserved. +# copyright@cpanel.net http://cpanel.net +# This code is subject to the cPanel license. Unauthorized copying is prohibited. + +package test::cpev::nics; + +use FindBin; + +use Test2::V0; +use Test2::Tools::Explain; +use Test2::Plugin::NoWarnings; +use Test2::Tools::Exception; + +use Test::MockFile 0.032; + +use lib $FindBin::Bin . "/lib"; +use Test::Elevate; + +use Test::MockModule qw/strict/; + +use cPstrict; + +my @mock_files; +for my $i ( 0 .. 2 ) { + my $mock_path = Test::MockFile->symlink( '/virtual', "/sys/class/net/eth$i" ); + push @mock_files, $mock_path; +} + +my ( $nic0, $nic1, $nic2 ); +my $mock_saferun = Test::MockModule->new('Cpanel::SafeRun::Errors'); +$mock_saferun->redefine( + saferunnoerror => sub { return sbin_ip_output( $nic0, $nic1, $nic2 ); }, +); + +$nic0 = 'lo'; +$nic1 = 'lo'; +$nic2 = 'lo'; +my @nics = Elevate::NICs::get_nics(); +is( + \@nics, + [], + 'Returns expected nics', +); + +$nic1 = 'eth0'; +@nics = Elevate::NICs::get_nics(); +is( + \@nics, + ['eth0'], + 'Returns expected nics', +); + +$nic2 = 'eth1'; +@nics = Elevate::NICs::get_nics(); +is( + \@nics, + [ 'eth0', 'eth1' ], + 'Returns expected nics', +); + +$nic0 = 'eth0'; +$nic1 = 'eth1'; +$nic2 = 'eth2'; +@nics = Elevate::NICs::get_nics(); +is( + \@nics, + [ 'eth0', 'eth1', 'eth2' ], + 'Returns expected nics', +); + +sub sbin_ip_output ( $nic0 = undef, $nic1 = undef, $nic2 = undef ) { + my $out = <<"EOS"; +1: $nic0: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 + link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 + inet 127.0.0.1/8 scope host lo + valid_lft forever preferred_lft forever + inet6 ::1/128 scope host + valid_lft forever preferred_lft forever +2: $nic1: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 + link/ether 46:6b:ce:04:98:99 brd ff:ff:ff:ff:ff:ff + inet 137.184.225.139/20 brd 137.184.239.255 scope global eth0 + valid_lft forever preferred_lft forever + inet 10.48.0.6/16 brd 10.48.255.255 scope global eth0 + valid_lft forever preferred_lft forever + inet6 fe80::446b:ceff:fe04:9899/64 scope link + valid_lft forever preferred_lft forever +3: $nic2: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 + link/ether ea:b0:b6:c9:51:ea brd ff:ff:ff:ff:ff:ff + inet 10.124.0.3/20 brd 10.124.15.255 scope global eth1 + valid_lft forever preferred_lft forever + inet6 fe80::e8b0:b6ff:fec9:51ea/64 scope link + valid_lft forever preferred_lft forever +EOS + + return $out; +} + +done_testing();