2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-10-20 22:55:40 +00:00

test(ubuntu): exercise genimage mirror selection

Read site settings from an isolated database and inspect the sources.list written into the image. Stop at an injected package-metadata failure without downloading packages.
This commit is contained in:
Vinícius Ferrão
2026-10-07 21:56:13 -03:00
parent 5013f85774
commit a4e14299ee
5 changed files with 104 additions and 84 deletions
+1
View File
@@ -24,6 +24,7 @@ jobs:
run: >-
prove xCAT-test/native/ubuntu_live_media_guardrails.t
xCAT-test/native/ubuntu_genimage_debootstrap_arch.t
xCAT-test/native/ubuntu_genimage_apt_mirror.t
- name: Run tests
if: ${{ !cancelled() }}
run: perl github_action_xcat_test.pl
+24 -1
View File
@@ -72,10 +72,33 @@ sub bootstrap {
my ($self, %options) = @_;
$self->prepare_genimage();
$self->write('work/packages/Packages.gz', 'package index fixture');
$self->write('work/site.json', encode_json({}));
$self->write('work/site.json', encode_json({
(exists $options{mirror} ? (ubuntu_apt_mirror => $options{mirror}) : ()),
}));
if ($options{bootstrap_success}) {
$self->write('work/bootstrap-success', '');
for my $tool (qw(mount umount)) {
my $path = $self->write("work/bin/$tool", "#!/bin/sh\nexit 0\n");
chmod(0755, $path) or die "chmod $path: $!";
}
my $chroot = $self->write('work/bin/chroot', <<'SH');
#!/bin/sh
case "$*" in
'/work/image/rootimg apt-get update')
printf '%s\n' "$@" > /work/apt-update.args
exit 23 ;;
*) printf 'Unexpected chroot command: %s\n' "$*" >&2; exit 97 ;;
esac
SH
chmod(0755, $chroot) or die "chmod $chroot: $!";
}
my $command = $self->write('work/bin/debootstrap', <<'SH');
#!/bin/sh
printf '%s\n' "$@" > /work/debootstrap.args
if [ -f /work/bootstrap-success ]; then
mkdir -p "$5/etc/apt" "$5/proc" "$5/usr/sbin" "$5/mnt"
exit 0
fi
exit 23
SH
chmod(0755, $command) or die "chmod $command: $!";
@@ -1,14 +1,20 @@
use strict;
use warnings;
use File::Slurper qw(read_text);
use JSON::PP qw(decode_json);
use File::Slurper qw(read_text write_binary);
use JSON::PP qw(decode_json encode_json);
use xCAT::Table;
my %values = (master => '192.0.2.1', httpport => '80',
%{ decode_json(read_text('/work/site.json')) });
my $site = xCAT::Table->new('site', -create => 1);
$site->setAttribs({ key => $_ }, { value => $values{$_} }) for keys %values;
for my $key (keys %values) {
$site->setAttribs({ key => $key }, { value => 'https://previous.example.invalid/ubuntu' })
if $key eq 'ubuntu_apt_mirror' && $values{$key} eq '';
$site->setAttribs({ key => $key }, { value => $values{$key} });
}
my $mirror_row = $site->getAttribs({ key => 'ubuntu_apt_mirror' }, qw(key value));
write_binary('/work/site-row.json', encode_json({ mirror => $mirror_row }));
$site->close();
exec 'perl', @ARGV;
die "exec genimage: $!";
@@ -0,0 +1,70 @@
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/../lib";
use JSON::PP qw(decode_json);
use Test::More;
use XCAT::Test::UbuntuImage;
plan skip_all => 'Linux filesystem namespaces are required' unless $^O eq 'linux';
my $archive = 'http://archive.ubuntu.com/ubuntu';
my $ports = 'http://ports.ubuntu.com/ubuntu-ports';
my $site = 'https://site.example.invalid/ubuntu';
my $explicit = 'https://image.example.invalid/ubuntu';
for my $case (
['amd64 default', {arch => 'x86_64'}, 'amd64', $archive, 'noble'],
['i386 default', {arch => 'x86'}, 'i386', $archive, 'noble'],
['POWER default', {arch => 'ppc64el'}, 'ppc64el', $ports, 'noble'],
['RISC-V default', {arch => 'riscv64'}, 'riscv64', $ports, 'noble'],
['s390x default', {arch => 's390x'}, 's390x', $ports, 'noble'],
['site overrides archive', {mirror => $site}, 'amd64', $site, 'noble'],
['site overrides ports', {arch => 'riscv64', mirror => $site}, 'riscv64', $site, 'noble'],
['cleared site keeps default', {arch => 'riscv64', mirror => ''}, 'riscv64', $ports, 'noble'],
['pkgdir overrides site', {mirror => $site,
pkgdir => "/work/packages,$explicit jammy main"}, 'amd64', $explicit, 'jammy'],
['first pkgdir mirror wins', {pkgdir =>
"/work/packages,$explicit noble main,$site jammy universe"}, 'amd64', $explicit, 'noble'],
) {
my ($name, $options, $arch, $mirror, $suite) = @$case;
subtest $name => sub {
my $image = XCAT::Test::UbuntuImage->new();
my ($status, $output, $args) = $image->bootstrap(%$options, bootstrap_success => 1);
if (exists $options->{mirror} && $options->{mirror} eq '') {
is_deeply(decode_json($image->read('work/site-row.json'))->{mirror},
{ key => 'ubuntu_apt_mirror' }, 'the cleared setting remains as an empty database row');
}
ok($status > 0 && ($status & 127) == 0,
'genimage exits unsuccessfully after the injected package-metadata failure') or diag($output);
is_deeply($args, ['--verbose', '--arch', $arch, $suite,
'/work/image/rootimg', $mirror], 'the selected mirror reaches debootstrap') or diag($output);
like($output, qr/Failed to update package metadata/,
'package-metadata failure is reported');
unlike($output, qr/Unexpected chroot command/, 'only the expected chroot command runs');
is($image->read('work/apt-update.args'), "/work/image/rootimg\napt-get\nupdate\n",
'APT uses the generated image root');
my @sources = grep { /\S/ } split /\n/,
$image->read('work/image/rootimg/etc/apt/sources.list');
my @expected = exists $options->{pkgdir}
? map { "deb $_" } grep { /^https?:/ } split /,/, $options->{pkgdir}
: map { "deb $mirror $_ main universe" } ($suite, "$suite-updates", "$suite-security");
unshift @expected, "deb [trusted=yes] http://192.0.2.1:80/work/packages $suite main restricted universe";
is_deeply([map { [split ' '] } @sources], [map { [split ' '] } @expected],
'image APT sources retain the local mirror and all configured remote pockets');
};
}
subtest 'explicit mirror without a suite' => sub {
my $image = XCAT::Test::UbuntuImage->new();
my ($status, $output, $args) = $image->bootstrap(
pkgdir => "/work/packages,$explicit");
is($status, 256, 'invalid mirror is rejected');
is($args, undef, 'debootstrap is not invoked');
like($output, qr/first http mirror path must includes http URL and distribute name/,
'rejection identifies the missing suite');
};
done_testing();
@@ -1,80 +0,0 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Spec;
use FindBin;
use Test::More;
# archive.ubuntu.com publishes amd64 and i386 only. A ppc64el or riscv64 netboot image built
# against it finds no package at all, and debootstrap fails before it copies anything, so the
# default mirror has to follow the image architecture.
#
# Driven by the real selection code: the two statements are extracted from genimage and
# evaluated here, so this test tracks the script rather than a copy of it.
my $repo_root = File::Spec->rel2abs( File::Spec->catdir( $FindBin::Bin, '..', '..' ) );
use lib "$FindBin::Bin/../../perl-xCAT";
require xCAT::Utils;
my $genimage_path = File::Spec->catfile(
$repo_root, 'xCAT-server', 'share', 'xcat', 'netboot', 'ubuntu', 'genimage'
);
plan skip_all => "genimage not found at $genimage_path" unless -f $genimage_path;
my $src = do { local $/; open my $fh, '<', $genimage_path or die $!; <$fh> };
my ($default) = $src =~ /^\s*(my \$default = \(\$uarch =~.*?;)\s*$/ms;
ok( defined $default, 'found the default mirror selection in genimage' )
or done_testing(), exit;
my ($pick) = $src =~ /^\s*(my \$mirror = \(defined \$aptmirror\[0\].*?;)\s*$/ms;
ok( defined $pick, 'found the mirror override in genimage' )
or done_testing(), exit;
# genimage does not read osimage.osarch directly: it converts it first, so the value the selection
# sees is whatever xCAT::Utils::debian_arch returns. Driving the xCAT token through that conversion
# is what catches a token the map does not know.
sub choose_osarch {
my ( $osarch, $site ) = @_;
return choose( xCAT::Utils->debian_arch($osarch), $site );
}
sub choose {
my ( $uarch, $site ) = @_;
my $set = defined $site ? "('$site')" : "()";
## no critic (BuiltinFunctions::ProhibitStringyEval)
my $mirror = eval "my \$uarch = '$uarch'; my \@aptmirror = $set; $default $pick \$mirror";
## use critic
die "failed to evaluate the genimage mirror selection: $@" if $@;
return $mirror;
}
is( choose('riscv64'), 'http://ports.ubuntu.com/ubuntu-ports',
'a riscv64 image takes the ports archive' );
is( choose('ppc64el'), 'http://ports.ubuntu.com/ubuntu-ports',
'a ppc64el image takes the ports archive' );
is( choose('amd64'), 'http://archive.ubuntu.com/ubuntu',
'an amd64 image keeps the main archive' );
is( choose('i386'), 'http://archive.ubuntu.com/ubuntu',
'an i386 image keeps the main archive' );
is( choose( 'riscv64', 'http://mirror.example.invalid/ubuntu' ),
'http://mirror.example.invalid/ubuntu',
'site.ubuntu_apt_mirror overrides the ports archive' );
is( choose( 'amd64', 'http://mirror.example.invalid/ubuntu' ),
'http://mirror.example.invalid/ubuntu',
'site.ubuntu_apt_mirror overrides the main archive' );
is( choose( 'riscv64', '' ), 'http://ports.ubuntu.com/ubuntu-ports',
'an empty site.ubuntu_apt_mirror does not blank the mirror' );
# The architecture reaches the selection as an xCAT osarch value, not as a Debian one.
is( choose_osarch('x86_64'), 'http://archive.ubuntu.com/ubuntu',
'osarch x86_64 reaches the archive' );
is( choose_osarch('x86'), 'http://archive.ubuntu.com/ubuntu',
'osarch x86 reaches the archive, which is where i386 lives' );
is( choose_osarch('riscv64'), 'http://ports.ubuntu.com/ubuntu-ports',
'osarch riscv64 reaches the ports archive' );
is( choose_osarch('ppc64el'), 'http://ports.ubuntu.com/ubuntu-ports',
'osarch ppc64el reaches the ports archive' );
done_testing();