diff --git a/xCAT-server/share/xcat/install/scripts/post.rhels10 b/xCAT-server/share/xcat/install/scripts/post.rhels10 index 8b6bba514..75e864747 100644 --- a/xCAT-server/share/xcat/install/scripts/post.rhels10 +++ b/xCAT-server/share/xcat/install/scripts/post.rhels10 @@ -6,15 +6,7 @@ [ "$MASTER_IP" ] || export MASTER_IP="#ENV:MASTER_IP#" #INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/scriptlib# -if command -v nmcli >/dev/null 2>&1; then - nmcli -g NAME,STATE con show | \ - awk -F: '$1 != "lo" && $2 == "activated" {print $1}' | \ - while IFS= read -r con_name - do - [ -n "$con_name" ] || continue - nmcli con mod "$con_name" connection.autoconnect yes - done -fi +xcat_enable_active_nm_autoconnect internet_repo_file_list="oracle-linux-ol10.repo uek-ol10.repo Rocky-AppStream.repo Rocky-BaseOS.repo Rocky-Extras.repo rocky.repo rocky-extras.repo CentOS-Base.repo centos.repo centos-addons.repo almalinux-ha.repo almalinux-nfv.repo almalinux-powertools.repo almalinux.repo almalinux-resilientstorage.repo almalinux-rt.repo" diff --git a/xCAT-server/share/xcat/install/scripts/post.rhels8 b/xCAT-server/share/xcat/install/scripts/post.rhels8 index 418557a9d..825d42af2 100644 --- a/xCAT-server/share/xcat/install/scripts/post.rhels8 +++ b/xCAT-server/share/xcat/install/scripts/post.rhels8 @@ -6,22 +6,6 @@ [ "$MASTER_IP" ] || export MASTER_IP="#ENV:MASTER_IP#" #INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/scriptlib# -activate_nm_connections() -{ - nmcli -g NAME,STATE con show | \ - awk -F: '$1 != "lo" && $2 == "activated" {print $1}' | \ - while IFS= read -r con_name - do - [ -n "$con_name" ] || continue - case "$XCATDEBUGMODE" in - "1"|"2") - msgutil_r "$MASTER_IP" "info" "set connection $con_name to be activated on system boot" "/var/log/xcat/xcat.log" - ;; - esac - nmcli con mod "$con_name" connection.autoconnect yes - done -} - activated_ifcfg=0 if compgen -G "/etc/sysconfig/network-scripts/ifcfg-*" >/dev/null 2>&1; then for i in /etc/sysconfig/network-scripts/ifcfg-* @@ -40,11 +24,11 @@ if compgen -G "/etc/sysconfig/network-scripts/ifcfg-*" >/dev/null 2>&1; then fi done - if [ "$activated_ifcfg" -eq 0 ] && command -v nmcli >/dev/null 2>&1; then - activate_nm_connections + if [ "$activated_ifcfg" -eq 0 ]; then + xcat_enable_active_nm_autoconnect 1 fi -elif command -v nmcli >/dev/null 2>&1; then - activate_nm_connections +else + xcat_enable_active_nm_autoconnect 1 fi # List of internal repos to be disabled diff --git a/xCAT-server/share/xcat/install/scripts/scriptlib b/xCAT-server/share/xcat/install/scripts/scriptlib index 3cd7ba10e..ab74bddb6 100644 --- a/xCAT-server/share/xcat/install/scripts/scriptlib +++ b/xCAT-server/share/xcat/install/scripts/scriptlib @@ -43,3 +43,23 @@ declare -F msgutil &>/dev/null || function msgutil { msgutil_r "" "$@" } +declare -F xcat_enable_active_nm_autoconnect &>/dev/null || function xcat_enable_active_nm_autoconnect { + local log_changes="${1:-}" + + command -v nmcli >/dev/null 2>&1 || return 0 + + nmcli -g NAME,STATE con show | \ + awk -F: '$1 != "lo" && $2 == "activated" {print $1}' | \ + while IFS= read -r con_name + do + [ -n "$con_name" ] || continue + if [ -n "$log_changes" ]; then + case "$XCATDEBUGMODE" in + "1"|"2") + msgutil_r "$MASTER_IP" "info" "set connection $con_name to be activated on system boot" "/var/log/xcat/xcat.log" + ;; + esac + fi + nmcli con mod "$con_name" connection.autoconnect yes + done +} diff --git a/xCAT-test/unit/networkmanager_autoconnect.t b/xCAT-test/unit/networkmanager_autoconnect.t new file mode 100644 index 000000000..07bb5ea50 --- /dev/null +++ b/xCAT-test/unit/networkmanager_autoconnect.t @@ -0,0 +1,284 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use File::Path qw(make_path); +use File::Spec; +use File::Temp qw(tempdir); +use FindBin; +use lib "$FindBin::Bin/../lib"; +use Test::More; + +use XCAT::Test::File qw(repo_path slurp_repo_file); + +my $scriptlib = repo_path( + File::Spec->catfile( + 'xCAT-server', 'share', 'xcat', 'install', 'scripts', 'scriptlib' + ) +); +-r $scriptlib or BAIL_OUT("$scriptlib is required"); + +is( system( 'bash', '-n', $scriptlib ), 0, + 'the install script library has valid Bash syntax' ); + +my $tmpdir = tempdir( CLEANUP => 1 ); +my $test_bin = File::Spec->catdir( $tmpdir, 'bin' ); +my $empty_bin = File::Spec->catdir( $tmpdir, 'empty-bin' ); +make_path( $test_bin, $empty_bin ); + +my $nmcli_log = File::Spec->catfile( $tmpdir, 'nmcli.log' ); +my $message_log = File::Spec->catfile( $tmpdir, 'messages.log' ); +my $fake_nmcli = File::Spec->catfile( $test_bin, 'nmcli' ); +write_file( $fake_nmcli, <<'SH' ); +#!/bin/sh +{ + printf 'nmcli' + for argument in "$@"; do + printf '\t<%s>' "$argument" + done + printf '\n' +} >>"$XCAT_TEST_NMCLI_LOG" + +if [ "$#" -eq 4 ] && [ "$1" = '-g' ] && [ "$2" = 'NAME,STATE' ] && + [ "$3" = 'con' ] && [ "$4" = 'show' ]; then + printf '%s' "$XCAT_TEST_NMCLI_OUTPUT" +fi +SH +chmod 0755, $fake_nmcli or die "Unable to make $fake_nmcli executable: $!"; + +my $driver = File::Spec->catfile( $tmpdir, 'run-helper' ); +write_file( $driver, <<'SH' ); +#!/bin/bash +msgutil_r() { + { + printf 'msgutil_r' + for argument in "$@"; do + printf '\t<%s>' "$argument" + done + printf '\n' + } >>"$XCAT_TEST_MESSAGE_LOG" +} +. "$XCAT_TEST_SCRIPTLIB" +xcat_enable_active_nm_autoconnect "$@" +SH +chmod 0755, $driver or die "Unable to make $driver executable: $!"; + +my $connections = join( "\n", + 'primary uplink:activated', 'backup:deactivated', + 'lo:activated', 'storage fabric:activated', + ':activated', '' ) . "\n"; + +is( run_program( $driver, connections => $connections ), 0, + 'the helper updates active non-loopback connections' ); +is( + read_file($nmcli_log), + command_line( '-g', 'NAME,STATE', 'con', 'show' ) + . command_line( 'con', 'mod', 'primary uplink', + 'connection.autoconnect', 'yes' ) + . command_line( 'con', 'mod', 'storage fabric', + 'connection.autoconnect', 'yes' ), + 'inactive, loopback, and blank connections are skipped without splitting names' +); +is( read_file($message_log), '', + 'logging stays disabled when the caller does not request it' ); + +is( + run_program( + $driver, + arguments => ['1'], + connections => "primary uplink:activated\n", + debug => '1' + ), + 0, + 'the helper accepts the legacy logging mode' +); +is( + read_file($message_log), + message_line( + '192.0.2.10', 'info', + 'set connection primary uplink to be activated on system boot', + '/var/log/xcat/xcat.log' + ), + 'debug mode logs the same connection activation message' +); + +is( + run_program( + $driver, + arguments => ['1'], + connections => "primary uplink:activated\n", + debug => '2' + ), + 0, + 'the helper preserves extended debug logging' +); +is( + read_file($message_log), + message_line( + '192.0.2.10', 'info', + 'set connection primary uplink to be activated on system boot', + '/var/log/xcat/xcat.log' + ), + 'extended debug mode logs the same connection activation message' +); + +is( + run_program( + $driver, + arguments => ['1'], + connections => "primary uplink:activated\n", + debug => '0' + ), + 0, + 'the legacy logging mode also supports non-debug operation' +); +is( read_file($message_log), '', + 'non-debug operation does not emit the optional message' ); + +is( + run_program( + $driver, + arguments => ['1'], + connections => "primary uplink:activated\n", + debug => '1', + nmcli => 0 + ), + 0, + 'the helper is a successful no-op without nmcli' +); +is( read_file($nmcli_log), '', 'nmcli is not invoked when it is unavailable' ); +is( read_file($message_log), '', + 'missing nmcli does not produce a connection activation message' ); + +my $rhels8 = stage_rendered_postscript('post.rhels8'); +my $rhels10 = stage_rendered_postscript('post.rhels10'); + +is( + run_program( + $rhels8, + connections => "primary uplink:activated\n", + debug => '1' + ), + 0, + 'the EL8 and EL9 install postscript uses the shared helper' +); +is( + read_file($message_log), + message_line( + '192.0.2.10', 'info', + 'set connection primary uplink to be activated on system boot', + '/var/log/xcat/xcat.log' + ), + 'the EL8 and EL9 caller preserves connection activation logging' +); +is( + read_file($nmcli_log), + command_line( '-g', 'NAME,STATE', 'con', 'show' ) + . command_line( 'con', 'mod', 'primary uplink', + 'connection.autoconnect', 'yes' ), + 'the EL8 and EL9 caller passes the connection name unchanged' +); + +is( + run_program( + $rhels10, + connections => "primary uplink:activated\n", + debug => '1' + ), + 0, + 'the EL10 install postscript uses the shared helper' +); +is( read_file($message_log), '', + 'the EL10 caller keeps connection activation logging disabled' ); +is( + read_file($nmcli_log), + command_line( '-g', 'NAME,STATE', 'con', 'show' ) + . command_line( 'con', 'mod', 'primary uplink', + 'connection.autoconnect', 'yes' ), + 'the EL10 caller passes the connection name unchanged' +); + +done_testing(); + +sub command_line { + return join( '', 'nmcli', map { "\t<$_>" } @_ ) . "\n"; +} + +sub message_line { + return join( '', 'msgutil_r', map { "\t<$_>" } @_ ) . "\n"; +} + +sub run_program { + my ( $program, %options ) = @_; + my $arguments = $options{arguments} // []; + my $has_nmcli = exists( $options{nmcli} ) ? $options{nmcli} : 1; + + write_file( $nmcli_log, '' ); + write_file( $message_log, '' ); + + local %ENV = %ENV; + $ENV{PATH} = $has_nmcli ? "$test_bin:$ENV{PATH}" : $empty_bin; + $ENV{XCATDEBUGMODE} = $options{debug} // '0'; + $ENV{MASTER_IP} = '192.0.2.10'; + $ENV{XCAT_TEST_MESSAGE_LOG} = $message_log; + $ENV{XCAT_TEST_NMCLI_LOG} = $nmcli_log; + $ENV{XCAT_TEST_NMCLI_OUTPUT} = $options{connections} // ''; + $ENV{XCAT_TEST_SCRIPTLIB} = $scriptlib; + + my $status = system( '/bin/bash', $program, @{$arguments} ); + return $status == -1 ? 255 : $status >> 8; +} + +sub stage_rendered_postscript { + my ($name) = @_; + my $postscript = slurp_repo_file( + File::Spec->catfile( + 'xCAT-server', 'share', 'xcat', 'install', 'scripts', $name + ) + ); + my $library = slurp_repo_file( + File::Spec->catfile( + 'xCAT-server', 'share', 'xcat', 'install', 'scripts', 'scriptlib' + ) + ); + my $include = '#INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/scriptlib#'; + $postscript =~ s/^\Q$include\E$/$library/m + or BAIL_OUT("Unable to render the scriptlib include in $name"); + + my $preamble = <<'SH'; +compgen() { return 1; } +sed() { :; } +msgutil_r() { + { + printf 'msgutil_r' + for argument in "$@"; do + printf '\t<%s>' "$argument" + done + printf '\n' + } >>"$XCAT_TEST_MESSAGE_LOG" +} +SH + $postscript =~ s/\A(#![^\n]*\n)/$1$preamble/ + or BAIL_OUT("Unable to stage the test preamble in $name"); + + my $destination = File::Spec->catfile( $tmpdir, $name ); + write_file( $destination, $postscript ); + chmod 0755, $destination + or die "Unable to make $destination executable: $!"; + return $destination; +} + +sub write_file { + my ( $path, $contents ) = @_; + open( my $fh, '>:raw', $path ) or die "Unable to write $path: $!"; + print {$fh} $contents; + close($fh) or die "Unable to close $path: $!"; +} + +sub read_file { + my ($path) = @_; + open( my $fh, '<:raw', $path ) or die "Unable to read $path: $!"; + my $contents = do { local $/; <$fh> }; + close($fh) or die "Unable to close $path: $!"; + return $contents; +}