From 509a60def234cdfa65663cd687e0d547bab884bc 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:43:49 -0300 Subject: [PATCH] test(provisioning): isolate install disk selection checks --- .github/workflows/xcat_test.yml | 7 + xCAT-test/lib/XCAT/Test/Sandbox.pm | 51 +++++++ xCAT-test/lib/XCAT/Test/Template.pm | 79 ++++++++++ xCAT-test/native/getinstdisk_selection.t | 158 ++++++++++++++++++++ xCAT-test/unit/getinstdisk_selection.t | 181 ----------------------- 5 files changed, 295 insertions(+), 181 deletions(-) create mode 100644 xCAT-test/lib/XCAT/Test/Sandbox.pm create mode 100644 xCAT-test/lib/XCAT/Test/Template.pm create mode 100644 xCAT-test/native/getinstdisk_selection.t delete mode 100644 xCAT-test/unit/getinstdisk_selection.t diff --git a/.github/workflows/xcat_test.yml b/.github/workflows/xcat_test.yml index 7159916c7..be4340b9d 100644 --- a/.github/workflows/xcat_test.yml +++ b/.github/workflows/xcat_test.yml @@ -29,6 +29,13 @@ jobs: - name: Check Debian probe payload permissions if: ${{ !cancelled() }} run: bats xCAT-test/native/probe_debian_permissions.bats + - name: Test isolated system scripts + if: ${{ !cancelled() }} + run: | + 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" - 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/lib/XCAT/Test/Sandbox.pm b/xCAT-test/lib/XCAT/Test/Sandbox.pm new file mode 100644 index 000000000..9bcc0734e --- /dev/null +++ b/xCAT-test/lib/XCAT/Test/Sandbox.pm @@ -0,0 +1,51 @@ +package XCAT::Test::Sandbox; + +use strict; +use warnings; +use Exporter qw(import); +use File::Path qw(make_path); +use File::Temp qw(tempdir); +use XCAT::Test::Process qw(run_command); + +our @EXPORT_OK = qw(sandbox_root sandbox_run); + +sub sandbox_root { + my $root = tempdir(CLEANUP => 1); + make_path(map { "$root/$_" } qw(bin boot etc log target tmp)); + return $root; +} + +sub sandbox_run { + my ($root, @command) = @_; + my $options = ref($command[0]) eq 'HASH' ? shift @command : {}; + my $bwrap = -x '/usr/bin/bwrap' ? '/usr/bin/bwrap' : '/bin/bwrap'; + die 'Install bubblewrap to run this test' unless -x $bwrap; + my @sandbox = ($bwrap, '--unshare-all', '--die-with-parent', '--new-session', + '--tmpfs', '/', '--proc', '/proc', '--dev', '/dev', '--tmpfs', '/run', + '--setenv', 'PATH', '/fixture/bin:/usr/bin:/bin', + '--setenv', 'LC_ALL', 'C', '--bind', $root, '/fixture', '--chdir', '/fixture'); + for my $directory (qw(/usr /bin /sbin /lib /lib64)) { + push @sandbox, '--ro-bind', $directory, $directory if -d $directory; + } + for my $mount (qw(boot etc target tmp)) { + push @sandbox, '--bind', "$root/$mount", "/$mount"; + } + push @sandbox, '--bind', "$root/log", '/var/log'; + push @sandbox, '--ro-bind', '/etc/alternatives', '/etc/alternatives' if -d '/etc/alternatives'; + my @mounts; + for my $kind (qw(read_only writable)) { + my $flag = $kind eq 'read_only' ? '--ro-bind' : '--bind'; + push @mounts, map { [$flag, $_, $options->{$kind}{$_}] } + keys %{$options->{$kind} // {}}; + } + for my $mount (sort { length($a->[2]) <=> length($b->[2]) || $a->[2] cmp $b->[2] } @mounts) { + push @sandbox, @$mount; + } + for my $name (keys %{$options->{env} // {}}) { + push @sandbox, '--setenv', $name, $options->{env}{$name}; + } + local %ENV; + return run_command(@sandbox, '/usr/bin/timeout', '--kill-after=5', '45', @command); +} + +1; diff --git a/xCAT-test/lib/XCAT/Test/Template.pm b/xCAT-test/lib/XCAT/Test/Template.pm new file mode 100644 index 000000000..eed942869 --- /dev/null +++ b/xCAT-test/lib/XCAT/Test/Template.pm @@ -0,0 +1,79 @@ +package XCAT::Test::Template; + +use strict; +use warnings; +use Exporter qw(import); +use File::Path qw(make_path); +use File::Temp qw(tempdir); +use File::Slurper qw(write_text); +use JSON::PP qw(encode_json); +use XCAT::Test::File qw(repo_path); +use XCAT::Test::Sandbox qw(sandbox_root sandbox_run); + +our @EXPORT_OK = qw(template_database set_row render_template); + +sub template_database { + my $dir = tempdir(CLEANUP => 1); + make_path("$dir/db", "$dir/install"); + symlink(repo_path('xCAT/postscripts'), "$dir/install/postscripts") or die $!; + $ENV{XCATROOT} = repo_path('xCAT-server'); + $ENV{XCATCFG} = "SQLite:$dir/db"; + require xCAT::Table; + require xCAT::Template; + require xCAT::Postage; + my %site = ( + installdir => "$dir/install", tftpdir => "$dir/tftpboot", + master => '192.0.2.1', timezone => 'UTC', domain => 'example.invalid', + xcatiport => 3002, xcatdport => 3001, httpport => 8080, + xcatdebugmode => 0, nodestatus => 1, secureroot => 1, + managedaddressmode => 'dhcp', + ); + set_row('site', {key => $_}, {value => $site{$_}}) for keys %site; + %::XCATSITEVALS = %site; + set_row('nodelist', {node => 'node'}, {groups => 'all'}); + set_row('noderes', {node => 'node'}, { + xcatmaster => '192.0.2.1', nfsserver => '192.0.2.1', installnic => 'mac', + }); + set_row('mac', {node => 'node'}, {mac => '52:54:00:12:34:56'}); + set_row('bootparams', {node => 'node'}, {kcmdline => 'console=ttyS0 quiet'}); + return $dir; +} + +sub set_row { + my ($name, $key, $values) = @_; + my $table = xCAT::Table->new($name, -create => 1) or die "Cannot create $name"; + $table->setAttribs($key, $values); + $table->close(); + return; +} + +sub render_template { + my ($database, @arguments) = @_; + my $root = sandbox_root(); + write_text("$root/arguments.json", encode_json(\@arguments)); + write_text("$root/render.pl", <<'PERL'); +use strict; +use warnings; +use File::Slurper qw(read_text); +use JSON::PP qw(decode_json); +use xCAT::Template; +use xCAT::Postage; +my $site = xCAT::Table->new('site'); +my @attributes = $site->getAllAttribs('key', 'value'); +%::XCATSITEVALS = map { $_->{key} => $_->{value} } @attributes; +$site->close(); +my $arguments = decode_json(read_text('/fixture/arguments.json')); +my $error = xCAT::Template->subvars(@$arguments); +die "$error\n" if $error; +PERL + my $source = repo_path('.'); + my ($status, $output) = sandbox_run($root, { + read_only => {$source => $source, repo_path('xCAT/postscripts') => '/install/postscripts'}, + writable => {$database => $database}, + env => {XCATROOT => $ENV{XCATROOT}, XCATCFG => $ENV{XCATCFG}}, + }, $^X, '-I' . repo_path('perl-xCAT'), '-I' . repo_path('xCAT-server/lib/perl'), '/fixture/render.pl'); + die "Template rendering failed ($status): $output" if $status; + return; +} + +1; diff --git a/xCAT-test/native/getinstdisk_selection.t b/xCAT-test/native/getinstdisk_selection.t new file mode 100644 index 000000000..e92768585 --- /dev/null +++ b/xCAT-test/native/getinstdisk_selection.t @@ -0,0 +1,158 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use FindBin; +use lib "$FindBin::Bin/../lib"; +use lib "$FindBin::Bin/../../perl-xCAT", "$FindBin::Bin/../../xCAT-server/lib/perl"; +use File::Path qw(make_path); +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); +use XCAT::Test::Template qw(template_database set_row render_template); + +plan skip_all => 'disk selection requires Linux namespaces' unless $^O eq 'linux'; +BAIL_OUT('run as root to create isolated dummy block devices') if $>; +my $script = repo_path('xCAT-server/share/xcat/install/scripts/getinstdisk'); +BAIL_OUT("missing disk selector: $script") unless -r $script; +my $database; + +sub selects { + my ($expected, $name, $disks, %options) = @_; + subtest $name => sub { + my $root = sandbox_root(); + make_path("$root/props", "$root/dev/md"); + my $partitions = "major minor #blocks name\n\n"; + my $minor = 0; + for my $disk (sort keys %$disks) { + my $attr = $disks->{$disk}; + $partitions .= sprintf " 8 %5d 524288000 %s\n", $minor++, $disk; + my $properties = "DEVTYPE=disk\n"; + $properties .= "ID_WWN=$attr->{wwn}\n" if $attr->{wwn}; + $properties .= "DEVPATH=$attr->{path}\n" if $attr->{path}; + write_binary("$root/props/$disk.props", $properties); + my $size = $attr->{size} // 1024000000; + my $attributes = qq{ ATTRS{size}=="$size"\n}; + $attributes .= qq{ DRIVERS=="$attr->{driver}"\n} if $attr->{driver}; + write_binary("$root/props/$disk.attrs", $attributes); + } + write_binary("$root/partitions", $partitions); + write_binary("$root/bin/udevadm", <<'SH'); +#!/bin/sh +name= +query= +while [ "$#" -gt 0 ]; do + case "$1" in + info) shift ;; + --name=*) name=${1#--name=}; shift ;; + --name|-n) name=$2; shift 2 ;; + --query=*) query=${1#--query=}; shift ;; + --query|-q) query=$2; shift 2 ;; + --attribute-walk|-a) query=attributes; shift ;; + *) exit 97 ;; + esac +done +name=${name#/dev/} +case "$query" in + property) cat "/fixture/props/$name.props" ;; + attributes) cat "/fixture/props/$name.attrs" ;; + *) exit 97 ;; +esac +SH + chmod 0755, "$root/bin/udevadm" or die $!; + for my $device (@{$options{blocks} // []}) { + system('mknod', "$root/dev/$device", 'b', '240', '0') == 0 or die "mknod: $?"; + } + write_binary("$root/dev/$_", '') for @{$options{regular} // []}; + my %mounts = ($script => '/getinstdisk', "$root/partitions" => '/proc/partitions'); + $mounts{"$root/dev/$_"} = "/dev/$_" for @{$options{blocks} // []}, @{$options{regular} // []}; + if ($options{no_awk}) { + write_binary("$root/bin/find", "#!/bin/sh\nexit 0\n"); + write_binary("$root/bin/awk", "#!/bin/sh\nprintf called >>/fixture/awk-calls\nexit 97\n"); + chmod 0755, "$root/bin/find", "$root/bin/awk" or die $!; + } + my $command = $options{logger} ? + 'msgutil_r() { printf "%s\n" "$*" >> /fixture/warnings; }; . /getinstdisk' : + '. /getinstdisk'; + if ($options{pre}) { + render_template($database, repo_path('xCAT-server/share/xcat/install/scripts/pre.rhels10'), + "$database/pre.sh", 'node', "$database/image.pkglist", '/install/rhels10/x86_64', + 'rh', undef, {xcatmaster => '192.0.2.1'}, osarch => 'x86_64'); + make_path("$root/libexec"); + write_binary("$root/libexec/platform-python", "#!/bin/sh\nexit 0\n"); + chmod 0755, "$root/libexec/platform-python" or die $!; + $mounts{"$root/libexec"} = '/usr/libexec'; + $mounts{"$database/pre.sh"} = '/pre.sh'; + for my $name (qw(logger vgs vgchange vgremove chown)) { + write_binary("$root/bin/$name", "#!/bin/sh\nexit 0\n"); + chmod 0755, "$root/bin/$name" or die $!; + } + write_binary("$root/bin/mknod", "#!/bin/sh\n: >\"\$1\"\n"); + chmod 0755, "$root/bin/mknod" or die $!; + $command = '. /pre.sh'; + } + for my $pass (1 .. 2) { + write_binary("$root/tmp/xcat.install_disk", "/dev/not-selected\n"); + my ($rc, $output) = sandbox_run($root, {read_only => \%mounts, + env => {MASTER_IP => '192.0.2.1', log_label => 'install'}}, + $options{pre} ? '/bin/bash' : '/bin/sh', '-c', $command); + is($rc, 0, "pass $pass completes") or diag($output); + unlike($output, qr/msgutil_r/, "pass $pass needs no absent installer logger") unless $options{logger}; + is(-f "$root/tmp/xcat.install_disk" ? scalar read_binary("$root/tmp/xcat.install_disk") : '', + "$expected\n", "pass $pass selects the expected disk"); + ok(!-e "$root/tmp/xcat.getinstalldisk", "pass $pass removes scan state"); + ok(!-e "$root/awk-calls", "pass $pass works without calling awk") if $options{no_awk}; + if ($options{pre}) { + my $partition = "$root/tmp/partitionfile"; + my @disk_rules = grep { /^ignoredisk\b/ } split /\n/, + (-f $partition ? scalar read_binary($partition) : ''); + is($disk_rules[-1], "ignoredisk --only-use=$expected", + "pass $pass uses the selected disk in the rendered installer partition plan"); + } + } + if ($options{logger}) { + is(scalar read_binary("$root/warnings"), + "192.0.2.1 warn Disk detection failed, defaulting to /dev/sda /var/log/xcat/xcat.log install\n" x 2, + 'default selection reaches the installer logger'); + } + }; + return; +} + +for my $no_awk (0, 1) { + subtest $no_awk ? 'without awk discovery' : 'with awk discovery' => sub { + my @cases = ( + ['/dev/sdb', 'direct disk before RAID', {sda => {driver => 'megaraid_sas'}, sdb => {driver => 'ahci'}}], + ['/dev/sda', 'RAID alone', {sda => {driver => 'megaraid_sas'}}], + ['/dev/sdb', 'direct disk before SAS', {sda => {driver => 'mpt3sas'}, sdb => {driver => 'ahci'}}], + ['/dev/sda', 'SAS before unclassified driver', {sda => {driver => 'mpt3sas'}, sdb => {driver => 'virtio_blk'}}], + ['/dev/nvme0n1', 'driverless NVMe', {nvme0n1 => {}}], + ['/dev/sda', 'empty scan', {}], + ['/dev/sdb', 'group wins over RAID WWN', {sda => {driver => 'megaraid_sas', wwn => '0x5001'}, sdb => {driver => 'ahci'}}], + ['/dev/sda', 'group wins when scanned first', {sda => {driver => 'ahci'}, sdb => {driver => 'megaraid_sas', wwn => '0x5002'}}], + ['/dev/sdb', 'WWN before no identifier', {sda => {driver => 'ahci'}, sdb => {driver => 'ahci', wwn => '0x5001'}}], + ['/dev/sdb', 'lower WWN', {sda => {driver => 'ahci', wwn => '0x5002'}, sdb => {driver => 'ahci', wwn => '0x5001'}}], + ['/dev/sda', 'path before no identifier', {sda => {driver => 'ahci', path => '/devices/pci/block/sda'}, sdb => {driver => 'ahci'}}], + ['/dev/xvda', 'scanned Xen disk', {xvda => {driver => 'vbd'}}], + ['/dev/xvdb', 'Xen driver ordering', {xvda => {driver => 'vbd'}, xvdb => {driver => 'ahci'}}], + ['/dev/sdb', 'ignore small media', {sda => {driver => 'ahci', size => 100}, sdb => {driver => 'virtio_blk'}}], + ); + selects(@$_, no_awk => $no_awk) for @cases; + }; +} +selects('/dev/md/Volume0_0', 'VROC numbered fallback', {}, blocks => ['md/Volume0_0']); +selects('/dev/md/Volume0', 'VROC unnumbered fallback', {}, blocks => ['md/Volume0']); +selects('/dev/md/Volume0_0', 'VROC preference over other fallbacks', {}, blocks => ['md/Volume0_0', 'md/Volume0', 'xvda']); +selects('/dev/xvda', 'Xen fallback', {}, blocks => ['xvda']); +selects('/dev/sdb', 'scanned disk before fallback devices', {sdb => {driver => 'ahci'}}, blocks => ['md/Volume0_0', 'xvda']); +selects('/dev/sda', 'regular files are not block devices', {}, regular => ['md/Volume0_0', 'md/Volume0', 'xvda']); +selects('/dev/sda', 'default selection with installer logger', {}, logger => 1); + +$database = template_database(); +set_row('site', {key => 'nodestatus'}, {value => 0}); +set_row('nodetype', {node => 'node'}, {os => 'rhels10', arch => 'x86_64', provmethod => 'install'}); +write_binary("$database/image.pkglist", ''); +selects('/dev/sdb', 'rendered RHEL 10 installer disk selection', + {sda => {driver => 'megaraid_sas'}, sdb => {driver => 'ahci'}}, pre => 1); + +done_testing(); diff --git a/xCAT-test/unit/getinstdisk_selection.t b/xCAT-test/unit/getinstdisk_selection.t deleted file mode 100644 index 00f040548..000000000 --- a/xCAT-test/unit/getinstdisk_selection.t +++ /dev/null @@ -1,181 +0,0 @@ -#!/usr/bin/env perl -use strict; -use warnings; - -use FindBin; -use File::Spec; -use File::Temp qw(tempdir); -use Test::More; - -my $repo_root = File::Spec->catdir( $FindBin::Bin, '..', '..' ); -my $script_dir = File::Spec->catdir( $repo_root, 'xCAT-server', 'share', 'xcat', 'install', 'scripts' ); - -# One script serves every installer, so one file is exercised here. -my @scripts = map { File::Spec->catfile( $script_dir, $_ ) } qw(getinstdisk); -plan skip_all => 'getinstdisk not found' if grep { !-r $_ } @scripts; -our $script; - -sub slurp { - open( my $fh, '<', $_[0] ) or die "Unable to read $_[0]: $!"; - my $c = do { local $/; <$fh> }; - close($fh); - return $c; -} - -# The script reads /proc/partitions and writes under /tmp, so each scenario -# runs a copy with those paths moved into its own sandbox, and a stub udevadm -# serves the device properties from fixture files. -sub run_scenario { - my (%disk) = @_; - my $sandbox = tempdir( CLEANUP => 1 ); - my $fixdir = "$sandbox/fix"; - my $bindir = "$sandbox/bin"; - mkdir $fixdir; - mkdir $bindir; - - my $body = slurp($script); - $body =~ s{/proc/partitions}{$sandbox/partitions}g; - $body =~ s{/tmp/xcat\.install_disk}{$sandbox/xcat.install_disk}g; - $body =~ s{/tmp/xcat\.getinstalldisk}{$sandbox/xcat.getinstalldisk}g; - $body =~ s{/dev/md/Volume0}{$sandbox/md/Volume0}g; - $body =~ s{"/dev/xvda"}{"$sandbox/xvda"}g; - open( my $sh, '>', "$sandbox/getinstdisk" ) or die $!; - print $sh $body; - close($sh); - - open( my $parts, '>', "$sandbox/partitions" ) or die $!; - print $parts "major minor #blocks name\n\n"; - my $minor = 0; - for my $name ( sort keys %disk ) { - printf $parts " 8 %5d 524288000 %s\n", $minor++, $name; - } - close($parts); - - for my $name ( sort keys %disk ) { - my %attr = %{ $disk{$name} }; - open( my $props, '>', "$fixdir/$name.props" ) or die $!; - print $props "ID_WWN=$attr{wwn}\n" if $attr{wwn}; - print $props "DEVPATH=$attr{path}\n" if $attr{path}; - print $props "DEVTYPE=disk\n"; - close($props); - open( my $attrs, '>', "$fixdir/$name.attrs" ) or die $!; - print $attrs qq{ ATTRS{size}=="1024000000"\n}; - print $attrs qq{ DRIVERS=="$attr{driver}"\n} if $attr{driver}; - my @models = $attr{models} ? @{ $attr{models} } : ( $attr{model} ? $attr{model} : () ); - print $attrs qq{ ATTRS{model}=="$_"\n} for @models; - close($attrs); - } - - open( my $udev, '>', "$bindir/udevadm" ) or die $!; - print $udev <<'UDEV'; -#!/bin/sh -for a in "$@"; do - case "$a" in - --name=*) name=${a#--name=} ;; - esac -done -name=${name#/dev/} -case "$*" in -*--query=property*) cat "$FIXDIR/$name.props" 2>/dev/null ;; -*--attribute-walk*) cat "$FIXDIR/$name.attrs" 2>/dev/null ;; -esac -exit 0 -UDEV - close($udev); - chmod 0755, "$bindir/udevadm"; - - local $ENV{FIXDIR} = $fixdir; - local $ENV{PATH} = "$bindir:$ENV{PATH}"; - local $ENV{MASTER_IP} = ''; - system("sh $sandbox/getinstdisk >$sandbox/log 2>&1"); - my $chosen = -r "$sandbox/xcat.install_disk" ? slurp("$sandbox/xcat.install_disk") : ''; - chomp $chosen; - return $chosen; -} - -# Every scenario asserts against both copies of the script. -sub selects { - my ( $expected, $name, %disk ) = @_; - for my $candidate (@scripts) { - local $script = $candidate; - my $variant = ( File::Spec->splitpath($candidate) )[2]; - is( run_scenario(%disk), $expected, "$name ($variant)" ); - } - return; -} - -# A direct attached disk wins over a RAID volume when both are present. The -# RAID volume sorts first by name, so the choice comes from the driver group. -selects( '/dev/sdb', 'the direct attached disk wins over the RAID volume', - sda => { driver => 'megaraid_sas' }, - sdb => { driver => 'ahci' } ); - -# A server with only RAID volumes still selects one. -selects( '/dev/sda', 'a RAID volume is selected when nothing better exists', - sda => { driver => 'megaraid_sas' } ); - -# A SAS host adapter loses to a direct attached disk, and still wins over a -# driver with no group of its own. -selects( '/dev/sdb', 'the direct attached disk wins over the host adapter', - sda => { driver => 'mpt3sas' }, - sdb => { driver => 'ahci' } ); - -selects( '/dev/sda', 'the host adapter wins over an unknown driver', - sda => { driver => 'mpt3sas' }, - sdb => { driver => 'virtio_blk' } ); - -# A driverless NVMe device still gets selected from the last group. -selects( '/dev/nvme0n1', 'an NVMe device is selected from the last group', - nvme0n1 => {} ); - -# No usable disk falls back to the documented default. -selects( '/dev/sda', 'no disks fall back to the default' ); - -# The driver group decides before the identifier. A disk that reports no WWN -# used to be dropped when another disk reported one, or to be ignored because -# the readback only opened the files of the last identifier class seen. -selects( '/dev/sdb', 'the direct attached disk wins when only the RAID volume reports a WWN', - sda => { driver => 'megaraid_sas', wwn => '0x5000cca0aaaa0001' }, - sdb => { driver => 'ahci' } ); - -selects( '/dev/sda', 'the direct attached disk wins when it is scanned first without a WWN', - sda => { driver => 'ahci' }, - sdb => { driver => 'megaraid_sas', wwn => '0x5000cca0aaaa0002' } ); - -# Within one driver group the identifier decides, and a disk that reports one -# is preferred, because that name is stable across reboots. -selects( '/dev/sdb', 'the disk with a WWN wins inside the group', - sda => { driver => 'ahci' }, - sdb => { driver => 'ahci', wwn => '0x5000cca0bbbb0001' } ); - -selects( '/dev/sdb', 'the lower WWN wins inside the group', - sda => { driver => 'ahci', wwn => '0x5000cca0bbbb0002' }, - sdb => { driver => 'ahci', wwn => '0x5000cca0bbbb0001' } ); - -selects( '/dev/sda', 'a path is preferred over no identifier at all', - sda => { driver => 'ahci', path => '/devices/pci0000:00/0000:00:1f.2/ata1/host0/target0:0:0/0:0:0:0/block/sda' }, - sdb => { driver => 'ahci' } ); - -# A Xen guest presents xvd names. The scan has to see them, because the -# fallback below it would take the first one without looking. -selects( '/dev/xvda', 'a Xen disk is scanned rather than assumed', - xvda => { driver => 'vbd' } ); - -selects( '/dev/xvdb', 'the better driver group wins among Xen disks', - xvda => { driver => 'vbd' }, - xvdb => { driver => 'ahci' } ); - -# Every installer includes the one script, which carries the fallbacks and the -# logging that the separate RHEL 10 copy used to hold on its own. -my $common = slurp( $scripts[0] ); -ok( !-e File::Spec->catfile( $script_dir, 'getinstdisk.rhels10' ), - 'the RHEL 10 copy of the script is gone' ); -like( slurp( File::Spec->catfile( $script_dir, 'pre.rhels10' ) ), - qr{/share/xcat/install/scripts/getinstdisk#}, - 'the RHEL 10 installer includes the common script' ); -like( $common, qr{-b /dev/md/Volume0_0}, 'the common script keeps the VROC fallback' ); -like( $common, qr{-b "/dev/xvda"}, 'the common script carries the Xen fallback' ); -like( $common, qr{command -v msgutil_r [^\n]*\n\s*msgutil_r }, - 'the failure log is guarded, because one installer does not define it' ); - -done_testing();