From 599cd3f32f11199e7d6ff4228a186a4ed009f925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Fri, 9 Oct 2026 17:45:40 -0300 Subject: [PATCH] test(kdump): execute per-node configuration in isolation --- .github/workflows/xcat_test.yml | 2 +- xCAT-test/native/enablekdump_per_node.t | 140 ++++++++++++++++++++++++ xCAT-test/unit/enablekdump_per_node.t | 137 ----------------------- 3 files changed, 141 insertions(+), 138 deletions(-) create mode 100644 xCAT-test/native/enablekdump_per_node.t delete mode 100644 xCAT-test/unit/enablekdump_per_node.t diff --git a/.github/workflows/xcat_test.yml b/.github/workflows/xcat_test.yml index be4340b9d..9f439a03f 100644 --- a/.github/workflows/xcat_test.yml +++ b/.github/workflows/xcat_test.yml @@ -35,7 +35,7 @@ jobs: workdir=$(sudo mktemp -d /tmp/xcat-script-tests.XXXXXX) trap 'sudo rm -rf -- "$workdir"' EXIT sudo cp -R xCAT-test xCAT-server perl-xCAT xCAT "$workdir/" - sudo sh -c 'cd "$1" && prove -v xCAT-test/native/getinstdisk_selection.t' sh "$workdir" + sudo sh -c 'cd "$1" && prove -v xCAT-test/native/getinstdisk_selection.t xCAT-test/native/enablekdump_per_node.t' sh "$workdir" - name: Prepare xCAT syslog directory if: ${{ !cancelled() }} run: sudo install -d -o syslog -g adm -m 0750 /var/log/xcat diff --git a/xCAT-test/native/enablekdump_per_node.t b/xCAT-test/native/enablekdump_per_node.t new file mode 100644 index 000000000..2f051ffdc --- /dev/null +++ b/xCAT-test/native/enablekdump_per_node.t @@ -0,0 +1,140 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use File::Path qw(make_path); +use FindBin; +use lib "$FindBin::Bin/../lib"; +use File::Slurper qw(read_binary write_binary); +use Test::More; +use XCAT::Test::File qw(repo_path); +use XCAT::Test::Sandbox qw(sandbox_root sandbox_run); + +plan skip_all => 'postscript targets Linux nodes' unless $^O eq 'linux'; +my $postscripts = repo_path('xCAT/postscripts'); +BAIL_OUT('enablekdump and xcatlib.sh are required') + unless -r "$postscripts/enablekdump" && -r "$postscripts/xcatlib.sh"; + +sub run_enablekdump { + my (%opt) = @_; + my $osver = $opt{osver}; + my $node = $opt{node} // 'n01'; + my $sysconfig = defined $opt{sysconfig} ? $opt{sysconfig} + : "KDUMP_COMMANDLINE=\"\"\nKDUMP_COMMANDLINE_APPEND=\"\"\n"; + + my $root = sandbox_root(); + write_binary("$root/calls", ''); + make_path("$root/etc/sysconfig", "$root/etc/systemd/system", "$root/mnt/kdumpsetup", "$root/sbin"); + write_binary("$root/etc/sysconfig/kdump", $sysconfig); + write_binary("$root/etc/systemd/system/kdump.service", "[Service]\n"); + write_binary("$root/etc/dracut.conf", "hostonly=yes\n"); + write_binary("$root/cmdline", $opt{cmdline} // 'console=ttyS0 crashkernel=256M unrelated=value'); + write_binary("$root/sbin/rpcinfo", "#!/bin/sh\nprintf '100003 4 tcp 2049 nfs\\n'\n"); + write_binary("$root/bin/mount", "#!/bin/sh\nprintf 'mount %s\\n' \"\$*\" >> /fixture/calls\n"); + write_binary("$root/bin/umount", "#!/bin/sh\nprintf 'umount %s\\n' \"\$*\" >> /fixture/calls\n"); + write_binary("$root/bin/hostname", "#!/bin/sh\nprintf 'fallback-node\\n'\n"); + write_binary("$root/bin/systemctl", <<'SH'); +#!/bin/sh +printf 'systemctl %s\n' "$*" >> /fixture/calls +test ! -e /etc/dracut.conf || exit 98 +exit "$RESTART_STATUS" +SH + chmod 0755, "$root/sbin/rpcinfo", map { "$root/bin/$_" } qw(mount umount hostname systemctl); + my ($rc, $output) = sandbox_run($root, { + read_only => {$postscripts => '/postscripts', "$root/cmdline" => '/proc/cmdline', + "$root/sbin" => '/usr/sbin', "$root/bin/mount" => '/bin/mount', + "$root/bin/umount" => '/bin/umount'}, + writable => {"$root/mnt" => '/mnt'}, + env => {DUMP => $opt{dump} // 'nfs://192.0.2.1/dumparea', XCAT => '192.0.2.1:eth0', + OSVER => $osver, ARCH => 'x86_64', NODE => $node, + RESTART_STATUS => $opt{restart_status} // 0}}, '/bin/bash', '/postscripts/enablekdump'); + is($rc, $opt{restart_status} // 0, 'the complete postscript returns the service result') or diag($output); + is(read_file("$root/etc/dracut.conf"), "hostonly=yes\n", 'dracut configuration is restored'); + ok(!-e "$root/tmp/dracut.conf", 'the temporary dracut copy is removed'); + + return { + root => $root, + target => "$root/mnt/kdumpsetup", + kdump_conf => read_file("$root/etc/kdump.conf"), + sysconfig => read_file("$root/etc/sysconfig/kdump"), + calls => read_file("$root/calls"), + }; +} + +sub read_file { + my ($p) = @_; + return '' unless -e $p; + return scalar read_binary($p); +} + +# --- RHEL 8: per-node, no shared-root writes ------------------------------- +{ + my $r = run_enablekdump(osver => 'rhels8.0', node => 'n01'); + + like($r->{kdump_conf}, qr{^path\s+/n01/var/crash$}m, + 'kdump.conf points the dump path at the node subdirectory'); + unlike($r->{kdump_conf}, qr{^path\s+/var/crash$}m, + 'kdump.conf does not use the shared /var/crash path'); + ok(-d "$r->{target}/n01/var/crash", 'the node subdirectory is created on the target'); + ok(!-e "$r->{target}/var/crash", 'nothing is created at the shared export root'); + ok(!-e "$r->{target}/proc", 'no dummy proc file is written on RHEL 8'); +} + +# --- RHEL 7: keeps the dracut workaround, but per-node ---------------------- +{ + my $r = run_enablekdump(osver => 'rhels7.9', node => 'n07'); + + like($r->{sysconfig}, qr{root=nfs:192\.0\.2\.1:/dumparea/n07}, + 'the RHEL 7 root= workaround points at the node subdirectory'); + ok(-e "$r->{target}/n07/proc", 'the RHEL 7 dummy proc is written under the node subdirectory'); + ok(!-e "$r->{target}/proc", 'the RHEL 7 dummy proc is not written at the shared root'); +} + +# --- RHEL 7 migration: a legacy shared root= is replaced, not kept ---------- +# A node configured by the previous script carries root=nfs:: +# in KDUMP_COMMANDLINE_APPEND. dracut takes the last root= on the command +# line, so leaving the legacy value behind would defeat the migration. +{ + my $legacy = qq{KDUMP_COMMANDLINE=""\n} + . qq{KDUMP_COMMANDLINE_APPEND="root=nfs:192.0.2.1:/dumparea rd.neednet=1 rootflags=nofail foo-root=bar rd.foo.root=bar"\n}; + my $r = run_enablekdump(osver => 'rhels7.9', node => 'n07', + sysconfig => $legacy); + + my ($append) = $r->{sysconfig} =~ m{^KDUMP_COMMANDLINE_APPEND="([^"]*)"}m; + my @roots = grep { /^root=/ } split ' ', defined $append ? $append : ''; + is(scalar @roots, 1, 'exactly one root= remains after migrating a legacy config'); + is($roots[0], 'root=nfs:192.0.2.1:/dumparea/n07', + 'the remaining root= points at the node subdirectory'); + like($append, qr{(?:^|\s)rd\.neednet=1(?:\s|$)}, + 'unrelated options on the legacy line are preserved'); + like($append, qr{(?:^|\s)rootflags=nofail(?:\s|$)}, + 'rootflags= is not mistaken for a root= token'); + like($append, qr{(?:^|\s)foo-root=bar(?:\s|$)}, + 'a root= suffix after a dash is not stripped'); + like($append, qr{(?:^|\s)rd\.foo\.root=bar(?:\s|$)}, + 'a root= suffix after a dot is not stripped'); + + # Re-running against its own output must not stack another root=. + my $r2 = run_enablekdump(osver => 'rhels7.9', node => 'n07', + sysconfig => $r->{sysconfig}); + my ($append2) = $r2->{sysconfig} =~ m{^KDUMP_COMMANDLINE_APPEND="([^"]*)"}m; + is($append2, $append, 'a second run leaves KDUMP_COMMANDLINE_APPEND unchanged'); +} + +{ + my $r = run_enablekdump(osver => 'rhels8.0', node => '', restart_status => 7); + my @calls = split /\n/, $r->{calls}; + ok(grep(/^systemctl\s+restart\s+kdump(?:\.service)?$/, @calls), + 'attempts to restart the dump service'); + like($calls[-1], qr{^umount\s+(?:\S+\s+)*/mnt/kdumpsetup$}, + 'a failed service restart still unmounts the dump target last'); + like($r->{kdump_conf}, qr{^path /fallback-node/var/crash$}m, 'missing NODE uses the short hostname'); + like($r->{sysconfig}, qr{console=ttyS0.*crashkernel=256M}, 'kdump keeps console and crashkernel options'); + unlike($r->{sysconfig}, qr{unrelated=value}, 'kdump omits unrelated kernel options'); +} +{ + my $r = run_enablekdump(osver => 'rhels8.0', dump => '', cmdline => ''); + is($r->{kdump_conf}, '', 'an unconfigured dump server writes no kdump configuration'); + is($r->{calls}, '', 'an unconfigured dump server does not mount or restart anything'); +} +done_testing(); diff --git a/xCAT-test/unit/enablekdump_per_node.t b/xCAT-test/unit/enablekdump_per_node.t deleted file mode 100644 index 087cf8736..000000000 --- a/xCAT-test/unit/enablekdump_per_node.t +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env perl -use strict; -use warnings; - -use File::Path qw(make_path); -use File::Temp qw(tempdir); -use FindBin; -use Test::More; - -my $script = "$FindBin::Bin/../../xCAT/postscripts/enablekdump"; -plan skip_all => 'enablekdump not found' unless -r $script; -# The postscript uses GNU sed -i, which behaves differently on BSD. -plan skip_all => 'postscript targets Linux nodes' unless $^O eq 'linux'; - -my $source = read_file($script); - -# Run the RHEL NFS path of enablekdump against a scratch tree. The dump target -# is a local directory standing in for the mounted NFS export, so we can check -# what the postscript writes to it and to /etc/kdump.conf. -sub run_enablekdump { - my (%opt) = @_; - my $osver = $opt{osver}; - my $node = $opt{node} || 'n01'; - my $sysconfig = defined $opt{sysconfig} ? $opt{sysconfig} - : "KDUMP_COMMANDLINE=\"\"\nKDUMP_COMMANDLINE_APPEND=\"\"\n"; - - my $root = tempdir(CLEANUP => 1); - make_path("$root/etc/sysconfig", "$root/target", "$root/bin"); - - # /etc/sysconfig/kdump must exist for the in-place seds to land. - write_file("$root/etc/sysconfig/kdump", $sysconfig); - # xcatlib.sh is sourced; only restartservice is needed and is a no-op here. - write_file("$root/xcatlib.sh", "restartservice(){ :; }\n"); - write_file("$root/bin/logger", "#!/bin/sh\nexit 0\n"); - chmod 0755, "$root/bin/logger"; - - my $src = $source; - # Redirect everything the postscript touches into the scratch tree. - $src =~ s{/etc/kdump\.conf}{$root/etc/kdump.conf}g; - $src =~ s{/etc/sysconfig/kdump}{$root/etc/sysconfig/kdump}g; - $src =~ s{/etc/dracut\.conf}{$root/etc/dracut.conf}g; - $src =~ s{/tmp/dracut\.conf}{$root/tmp/dracut.conf}g; - # No real NFS server: make the version probe empty so the mount is skipped; - # the per-node mkdir and kdump.conf rendering still run against the target. - $src =~ s{/usr/sbin/rpcinfo}{/bin/false}g; - $src =~ s{/bin/mount}{true}g; - $src =~ s{/bin/umount}{true}g; - # Point the staging mount point at the scratch target that stands in for the - # mounted NFS export. - $src =~ s{/mnt/kdumpsetup}{$root/target}g; - - write_file("$root/enablekdump", $src); - chmod 0755, "$root/enablekdump"; - - my $dump = "nfs://10.0.0.1/dumparea"; - system(qq{cd '$root' && DUMP='$dump' XCAT='10.0.0.1:eth0' OSVER='$osver' } - . qq{ARCH='x86_64' NODE='$node' PATH="$root/bin:\$PATH" ./enablekdump >/dev/null 2>&1}); - - return { - root => $root, - target => "$root/target", - kdump_conf => read_file("$root/etc/kdump.conf"), - sysconfig => read_file("$root/etc/sysconfig/kdump"), - }; -} - -sub read_file { - my ($p) = @_; - return '' unless -e $p; - open my $fh, '<', $p or die "open $p: $!"; - local $/; - return <$fh>; -} - -sub write_file { - my ($p, $c) = @_; - open my $fh, '>', $p or die "open $p: $!"; - print {$fh} $c; - close $fh; - return; -} - -# --- RHEL 8: per-node, no shared-root writes ------------------------------- -{ - my $r = run_enablekdump(osver => 'rhels8.0', node => 'n01'); - - like($r->{kdump_conf}, qr{^path\s+/n01/var/crash$}m, - 'kdump.conf points the dump path at the node subdirectory'); - unlike($r->{kdump_conf}, qr{^path\s+/var/crash$}m, - 'kdump.conf does not use the shared /var/crash path'); - ok(-d "$r->{target}/n01/var/crash", 'the node subdirectory is created on the target'); - ok(!-e "$r->{target}/var/crash", 'nothing is created at the shared export root'); - ok(!-e "$r->{target}/proc", 'no dummy proc file is written on RHEL 8'); -} - -# --- RHEL 7: keeps the dracut workaround, but per-node ---------------------- -{ - my $r = run_enablekdump(osver => 'rhels7.9', node => 'n07'); - - like($r->{sysconfig}, qr{root=nfs:10\.0\.0\.1:/dumparea/n07}, - 'the RHEL 7 root= workaround points at the node subdirectory'); - ok(-e "$r->{target}/n07/proc", 'the RHEL 7 dummy proc is written under the node subdirectory'); - ok(!-e "$r->{target}/proc", 'the RHEL 7 dummy proc is not written at the shared root'); -} - -# --- RHEL 7 migration: a legacy shared root= is replaced, not kept ---------- -# A node configured by the previous script carries root=nfs:: -# in KDUMP_COMMANDLINE_APPEND. dracut takes the last root= on the command -# line, so leaving the legacy value behind would defeat the migration. -{ - my $legacy = qq{KDUMP_COMMANDLINE=""\n} - . qq{KDUMP_COMMANDLINE_APPEND="root=nfs:10.0.0.1:/dumparea rd.neednet=1 rootflags=nofail foo-root=bar rd.foo.root=bar"\n}; - my $r = run_enablekdump(osver => 'rhels7.9', node => 'n07', - sysconfig => $legacy); - - my ($append) = $r->{sysconfig} =~ m{^KDUMP_COMMANDLINE_APPEND="([^"]*)"}m; - my @roots = grep { /^root=/ } split ' ', defined $append ? $append : ''; - is(scalar @roots, 1, 'exactly one root= remains after migrating a legacy config'); - is($roots[0], 'root=nfs:10.0.0.1:/dumparea/n07', - 'the remaining root= points at the node subdirectory'); - like($append, qr{(?:^|\s)rd\.neednet=1(?:\s|$)}, - 'unrelated options on the legacy line are preserved'); - like($append, qr{(?:^|\s)rootflags=nofail(?:\s|$)}, - 'rootflags= is not mistaken for a root= token'); - like($append, qr{(?:^|\s)foo-root=bar(?:\s|$)}, - 'a root= suffix after a dash is not stripped'); - like($append, qr{(?:^|\s)rd\.foo\.root=bar(?:\s|$)}, - 'a root= suffix after a dot is not stripped'); - - # Re-running against its own output must not stack another root=. - my $r2 = run_enablekdump(osver => 'rhels7.9', node => 'n07', - sysconfig => $r->{sysconfig}); - my ($append2) = $r2->{sysconfig} =~ m{^KDUMP_COMMANDLINE_APPEND="([^"]*)"}m; - is($append2, $append, 'a second run leaves KDUMP_COMMANDLINE_APPEND unchanged'); -} - -done_testing();