diff --git a/xCAT-server/lib/perl/xCAT/Template.pm b/xCAT-server/lib/perl/xCAT/Template.pm index 79b26101f..4147ad137 100644 --- a/xCAT-server/lib/perl/xCAT/Template.pm +++ b/xCAT-server/lib/perl/xCAT/Template.pm @@ -1850,6 +1850,18 @@ sub ubuntu_subiquity_apt_mirror # The key curtin names in the Deb822 source it writes for the primary apt mirror on 24.04 and later. my $UBUNTU_ARCHIVE_KEYRING = '/usr/share/keyrings/ubuntu-archive-keyring.gpg'; +# The apt lines that keep the recommended packages out of the install. ospkgs installs without +# them and the installer matches it. ppc64el is the exception: curtin installs a bootloader +# package of its own for UEFI and for s390x only, and on a PReP machine install_grub runs +# "dpkg-reconfigure grub-ieee1275" on a package the Ubuntu kernel image only recommends. +sub ubuntu_subiquity_no_recommends_lines +{ + my ($osarch) = @_; + + return () if defined($osarch) && $osarch =~ /^ppc64/i; + return (q( conf: 'APT::Install-Recommends "false";')); +} + sub ubuntu_subiquity_apt_config { my ($media_dir, $osarch, $pkgdirs) = @_; @@ -1877,7 +1889,7 @@ sub ubuntu_subiquity_apt_config ' apt:', ' preserve_sources_list: false', ' geoip: false', - q( conf: 'APT::Install-Recommends "false";'), + ubuntu_subiquity_no_recommends_lines($osarch), ' mirror-selection:', ' primary:', " - uri: $online_mirror", @@ -1916,7 +1928,7 @@ sub ubuntu_subiquity_apt_config ' preserve_sources_list: false', ' fallback: offline-install', ' geoip: false', - q( conf: 'APT::Install-Recommends "false";'), + ubuntu_subiquity_no_recommends_lines($osarch), ' disable_suites:', ' - updates', ' - backports', diff --git a/xCAT-server/share/xcat/install/ubuntu/compute.subiquity.tmpl b/xCAT-server/share/xcat/install/ubuntu/compute.subiquity.tmpl index 39cbc5496..ffd5b545d 100644 --- a/xCAT-server/share/xcat/install/ubuntu/compute.subiquity.tmpl +++ b/xCAT-server/share/xcat/install/ubuntu/compute.subiquity.tmpl @@ -127,4 +127,11 @@ autoinstall: # monitor that died and never came back is #7759. - ['bash', '-c', 'xm=#XCATVAR:XCATMASTER#; port="#TABLEBLANKOKAY:site:key=xcatiport:value#"; [ -n "$port" ] || port=3002; ok=0; for i in 1 2 3 4 5; do if exec 3<>/dev/tcp/$xm/$port; then if read -r -t 10 hello <&3 && [ "$hello" = "ready" ]; then printf "next\n" >&3; if read -r -t 10 ack <&3 && [ "$ack" = "done" ]; then ok=1; fi; fi; exec 3>&- 3<&-; [ "$ok" = 1 ] && break; fi; sleep 5; done; if [ "$ok" != 1 ]; then echo "xcat: FAILED to flip $(hostname) to local-disk boot via $xm:$port; the node will PXE back into the installer" >>/target/var/log/xcat/xcat.log; fi; exit 0'] error-commands: - - tar -c --ignore-failed-read --transform='s/^/#HOSTNAME#-logs\//' /var/crash /var/log/installer /tmp/pre-install.log /autoinstall.yaml 2>/dev/null |nc -l 8080 + # Subiquity waits for every error command to return. "nc -l 8080" waits for a collector, which + # an unattended install does not have. Keep the archive on the installer, and print the end of + # the curtin log to the console, which the management node records. + # + # Both destinations are read from the environment so a test can point them at its own + # directory. The installer sets neither, so an install writes where it always did. + - ['sh', '-c', 'tar -c --ignore-failed-read --transform="s/^/#HOSTNAME#-logs\//" /var/crash /var/log/installer /tmp/pre-install.log /autoinstall.yaml >"${XCAT_ERROR_ARCHIVE:-/run/#HOSTNAME#-logs.tar}" 2>/dev/null; exit 0'] + - ['sh', '-c', 'tail -n 80 /var/log/installer/curtin-install.log >"${XCAT_ERROR_CONSOLE:-/dev/console}" 2>/dev/null; exit 0'] diff --git a/xCAT-test/bats/ubuntu_subiquity_error_commands.bats b/xCAT-test/bats/ubuntu_subiquity_error_commands.bats new file mode 100644 index 000000000..250f27833 --- /dev/null +++ b/xCAT-test/bats/ubuntu_subiquity_error_commands.bats @@ -0,0 +1,92 @@ +#!/usr/bin/env bats +# +# Subiquity waits for every error command to return before it reports the failure. An error +# command that waits for a collector an unattended install does not have therefore holds the +# node until the provisioning timeout. +# +# Run the error commands, with the programs that would reach the host or the network replaced, +# and check that they return and that they write the end of the curtin log where the caller +# points. + +load 'helpers/shell_source' + +setup() +{ + TEMPLATE="$(repo_path 'xCAT-server/share/xcat/install/ubuntu/compute.subiquity.tmpl')" + [ -r "$TEMPLATE" ] || skip "$TEMPLATE is required" + export TEMPLATE +} + +# One command per list item of the error-commands block. The list form ['sh', '-c', '...'] +# carries the command in its last element; a plain item is the command itself. +error_commands() +{ + awk ' + /^ error-commands:$/ { copy = 1; next } + copy && !/^ [-#]/ { exit } + copy && /^ - / { + found = 1 + line = substr($0, 7) + if (match(line, /^\[[^]]*, .-c., .*\]$/)) { + sub(/^\[[^]]*, .-c., ./, "", line) + sub(/.\]$/, "", line) + } + print line + } + END { if (!found) exit 1 } + ' "$TEMPLATE" +} + +# bash resolves a function ahead of PATH, so the commands run as written while nothing reaches +# the host or the network. The nc shadow waits the way a listener with no collector waits. +# +# Shadowing a command does not stop the shell opening the file the command redirects into: the +# redirection is the shell's, and it happens whether or not tar runs. So the archive path is +# taken from the environment too, and pointed inside the test's own directory. Without that, +# this test would truncate /run/-logs.tar on the host running it, and CI runs as root. +run_error_commands() +{ + local script="${BATS_TEST_TMPDIR}/error-commands.sh" command + + : >"$script" + { + printf "export XCAT_ERROR_CONSOLE='%s'\n" "$CONSOLE" + printf "export XCAT_ERROR_ARCHIVE='%s'\n" "$ARCHIVE" + printf 'nc() { sleep 300; }\n' + printf 'tar() { echo XCAT_LOGS_ARCHIVE; }\n' + printf 'tail() { echo XCAT_CURTIN_LOG_TAIL; }\n' + } >>"$script" + + while IFS= read -r command; do + # Subiquity runs each error command on its own, so a command that ends in "exit 0" + # must not end the others. + printf '( %s )\n' "${command//\#HOSTNAME\#/testnode}" >>"$script" + done < <(error_commands) + + timeout 10 bash "$script" +} + +@test "the error commands return instead of waiting for someone to collect the logs" { + [ -n "$(error_commands)" ] + CONSOLE="${BATS_TEST_TMPDIR}/console" + ARCHIVE="${BATS_TEST_TMPDIR}/logs.tar" + + run run_error_commands + [ "$status" -ne 124 ] + grep -q XCAT_CURTIN_LOG_TAIL "$CONSOLE" +} + +@test "the log archive is written where the caller points, and nowhere else" { + CONSOLE="${BATS_TEST_TMPDIR}/console" + ARCHIVE="${BATS_TEST_TMPDIR}/logs.tar" + + run run_error_commands + [ "$status" -ne 124 ] + + # The archive command redirects into the path it was given, so its output IS the proof + # that the redirection went there and not to the default under /run. There is nothing to + # add by looking at the default path: whether /run/testnode-logs.tar exists says nothing + # about this test, because anything else may have created it, and its absence would be + # just as true had the override never worked. + grep -q XCAT_LOGS_ARCHIVE "$ARCHIVE" +} diff --git a/xCAT-test/unit/ubuntu_subiquity_pkglist.t b/xCAT-test/unit/ubuntu_subiquity_pkglist.t index 05d15c361..3daf4ac49 100644 --- a/xCAT-test/unit/ubuntu_subiquity_pkglist.t +++ b/xCAT-test/unit/ubuntu_subiquity_pkglist.t @@ -193,8 +193,21 @@ is( $render->( $pkglist, environvar => 'ACCEPT_EULA=Y' ), $without, }; like( $apt_render->(), qr{URIs: http://mirror\.example/ubuntu}, 'the pkgdir mirror joins the installer sources' ); like( $apt_render->(), qr{^ conf: 'APT::Install-Recommends "false";'$}m, 'the installer installs without recommended packages, as ospkgs does' ); + # On a PReP machine curtin installs no bootloader package of its own: install_missing_packages + # covers UEFI and s390x only, and install_grub then runs "dpkg-reconfigure grub-ieee1275". + # The Ubuntu kernel image recommends grub-ieee1275, so the package reaches the target only + # when the installer keeps recommended packages. + unlike( $apt_render->( osarch => 'ppc64el' ), qr{APT::Install-Recommends}, + 'except on ppc64el, where the bootloader the installer configures is a recommended package' ); unlike( $apt_render->( environvar => 'http_proxy=http://proxy.example:3128' ), qr{mirror\.example|xcat-pkgdir}, 'but not for an osimage with environvar, whose mirrors may need those variables' ); + + # The offline configuration installs from the media alone and carries the same setting. + local *xCAT::Template::ubuntu_subiquity_apt_mirror = sub { '' }; + like( $apt_render->(), qr{^ conf: 'APT::Install-Recommends "false";'$}m, + 'an offline install drops the recommended packages as well' ); + unlike( $apt_render->( osarch => 'ppc64el' ), qr{APT::Install-Recommends}, + 'and keeps them on ppc64el for the same reason' ); } done_testing();