From 042804fe5ed93b2cba6fbca5984a5834274899c7 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, 16 Oct 2026 18:45:07 -0300 Subject: [PATCH 1/2] fix(genimage): create the /run mount point before mounting it mount_chroot bind-mounts the host /run into the image root before the run directory exists there. On the first build of a new image, mount fails with "mount point does not exist". genimage does not check the result, so the packages install without the host /run. Create the directory first, as mount_chroot already does for /proc, /sys and /dev. The mount also uses the subroutine argument instead of the global image path. Both have the same value at every call site. --- xCAT-server/share/xcat/netboot/rh/genimage | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xCAT-server/share/xcat/netboot/rh/genimage b/xCAT-server/share/xcat/netboot/rh/genimage index 1afc5d325..e6c2543ae 100755 --- a/xCAT-server/share/xcat/netboot/rh/genimage +++ b/xCAT-server/share/xcat/netboot/rh/genimage @@ -123,7 +123,8 @@ sub mount_chroot { } if (-d "/run") { - system("mount --bind /run $rootimg_dir/run"); + system("mkdir -p $rootimage_dir/run"); + system("mount --bind /run $rootimage_dir/run"); } } From 0b70a389a379929f68f8c833fd44cc547b6fcc53 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, 16 Oct 2026 18:45:20 -0300 Subject: [PATCH 2/2] test(genimage): check the chroot mounts of a new RPM image The image caller tests create the run directory in their fixture image, so they did not see a bind mount to a missing mount point. Run rh/genimage on a new and on an existing image directory. The mount fixture fails like mount(8) when the mount point is missing, and the package manager fixture records the mounts in place when the packages install. Cover EL9 through the rh and rocky entry points, and EL6, which does not mount /proc and /sys. The pull-request workflow runs the test. --- .github/workflows/xcat_test.yml | 3 ++ xCAT-test/native/README.md | 8 +-- xCAT-test/native/rh_genimage_chroot_mounts.t | 56 ++++++++++++++++++++ 3 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 xCAT-test/native/rh_genimage_chroot_mounts.t diff --git a/.github/workflows/xcat_test.yml b/.github/workflows/xcat_test.yml index 7159916c7..86d3d6c88 100644 --- a/.github/workflows/xcat_test.yml +++ b/.github/workflows/xcat_test.yml @@ -26,6 +26,9 @@ jobs: - name: Exercise image package policy if: ${{ !cancelled() }} run: prove xCAT-test/native/el10_image_package_policy.t + - name: Exercise image chroot mounts + if: ${{ !cancelled() }} + run: prove xCAT-test/native/rh_genimage_chroot_mounts.t - name: Check Debian probe payload permissions if: ${{ !cancelled() }} run: bats xCAT-test/native/probe_debian_permissions.bats diff --git a/xCAT-test/native/README.md b/xCAT-test/native/README.md index f5d6f834c..3678c74f4 100644 --- a/xCAT-test/native/README.md +++ b/xCAT-test/native/README.md @@ -86,15 +86,17 @@ Net::DNS: ``` prove xCAT-test/native/genimage_network_drivers.t \ - xCAT-test/native/el10_image_package_policy.t + xCAT-test/native/el10_image_package_policy.t \ + xCAT-test/native/rh_genimage_chroot_mounts.t ``` These cases run complete callers in private filesystems with no network. They check driver configuration after real driver-disk loading, media-import -records and parsed RPM installation scriptlets. +records, parsed RPM installation scriptlets and the mounts that a new RPM +image has when its packages install. External package downloads, service management, disk mounts and initrd creation use command fixtures. These cases do not boot an image or qualify hardware. -The two Perl image caller tests fail if run as root. +The Perl image caller tests fail if run as root. The default BATS suite also runs `networkmanager_postscript.bats`. It renders installation postscripts and checks their network configuration in isolation. diff --git a/xCAT-test/native/rh_genimage_chroot_mounts.t b/xCAT-test/native/rh_genimage_chroot_mounts.t new file mode 100644 index 000000000..5096d0d4c --- /dev/null +++ b/xCAT-test/native/rh_genimage_chroot_mounts.t @@ -0,0 +1,56 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use FindBin; +use lib "$FindBin::Bin/../lib"; +use File::Path qw(make_path); +use Test::More; +use XCAT::Test::ImageSandbox; + +plan skip_all => 'Linux namespaces require Linux' unless $^O eq 'linux'; +die('Run the image caller tests as an unprivileged user') unless $>; + +my $image = 'work/image/rootimg'; +for my $case (['rh', 'rhels9.6'], ['rocky', 'rocky9.6'], ['rh', 'rhels6.10']) { + my ($builder, $os) = @$case; + my @expected = ("--bind /run /$image/run"); + unshift @expected, "proc /$image/proc -t proc", "sysfs /$image/sys -t sysfs" unless $os eq 'rhels6.10'; + for my $build (qw(first repeated)) { + subtest "$builder $os $build build" => sub { + my $box = XCAT::Test::ImageSandbox->new(); + make_path("$box->{root}/install/$os/x86_64/repodata"); + make_path(map { "$box->{root}/$image/$_" } qw(proc sys dev run)) if $build eq 'repeated'; + $box->write('work/compute.pkglist', "bash\n"); + + # Like mount(8), the fixture refuses a mount point that does not exist. + $box->command('mount', <<'SH'); +mount=$* +operands=0 +while [ "$#" -gt 0 ]; do + case $1 in + -t|-o) shift ;; + -*) ;; + *) operands=$((operands + 1)); [ "$operands" -eq 2 ] && target=$1 ;; + esac + shift +done +if [ ! -d "$target" ]; then + printf 'mount: %s: mount point does not exist.\n' "$target" >&2 + exit 32 +fi +printf '%s\n' "$mount" >>/work/mounts +SH + $box->command($_, ": >>/work/mounts\ncat /work/mounts >/work/install-mounts\nexit 1\n") for qw(dnf yum); + + my ($rc, $output, $error) = $box->run('perl', "/repo/xCAT-server/share/xcat/netboot/$builder/genimage", + '-a', 'x86_64', '-o', $os, '-p', 'compute', '--pkglist', '/work/compute.pkglist', + '--srcdir', "/install/$os/x86_64", '--rootimgdir', '/work/image', 'fixture-image'); + ok(-f "$box->{root}/work/install-mounts", 'the build reaches the package installation') + or diag($output, $error); + unlike($error, qr/mount point does not exist/, 'every mount point exists before its mount'); + my $mounts = -f "$box->{root}/work/install-mounts" ? $box->read('work/install-mounts') : ''; + is_deeply([split /\n/, $mounts], \@expected, 'packages install with the host /run in the image'); + }; + } +} +done_testing();