mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-09-26 01:34:05 +00:00
test(xcat-core): the Ubuntu Genesis image ships without a DHCP client
builddeb-genesis-base builds the Genesis payload in the host root. Its REQUIRED_PACKAGES list omits isc-dhcp-client, ifenslave and util-linux-extra, so dracut cannot find dhclient, ifenslave or hwclock. dracut_install reports each missing command and returns, the script does not read dracut's result, and the .deb is packaged with the holes. A Genesis image with no dhclient never obtains a provisioning lease, so the node netboots and never reports in. The EL path gates the payload: xCAT-genesis-base.spec runs verify-genesis-payload. The Ubuntu path runs no gate at all. genesis_ubuntu_build_root.t sources the Ubuntu dracut module with dracut_install shadowed, calls install(), and reads back the commands it installs unconditionally. It then evaluates the REQUIRED_PACKAGES assignment and asserts the build root supplies each one. Five of its eight assertions fail on this commit. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
This commit is contained in:
Executable
+94
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env perl
|
||||
# The Ubuntu Genesis build root must carry every command the Ubuntu dracut module marks
|
||||
# mandatory. dracut_install reports a missing command and returns, so a command the build
|
||||
# root does not supply leaves a hole in the image and the build still exits 0.
|
||||
#
|
||||
# The mandatory list is read by RUNNING the module: module-setup.sh is sourced with
|
||||
# dracut_install shadowed, _dracut_install_opt neutralised (its callers are optional by
|
||||
# construction), and install() is called. The package list is read by extracting the
|
||||
# REQUIRED_PACKAGES assignment from builddeb-genesis-base and evaluating it.
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use Test::More;
|
||||
|
||||
use XCAT::Test::File qw(repo_path);
|
||||
|
||||
my $builder = repo_path('xCAT-genesis-builder/builddeb-genesis-base');
|
||||
my $module = repo_path('xCAT-genesis-builder/dracut_105/ubuntu/module-setup.sh');
|
||||
plan skip_all => 'builddeb-genesis-base not found' unless -f $builder;
|
||||
plan skip_all => 'ubuntu module-setup.sh not found' unless -f $module;
|
||||
plan tests => 8;
|
||||
|
||||
# Commands the Ubuntu dracut module marks mandatory that a minimal Ubuntu server root does
|
||||
# NOT already provide, and the package that supplies each one. Every entry here has to be in
|
||||
# REQUIRED_PACKAGES or the image ships without the command.
|
||||
my %PACKAGE_FOR = (
|
||||
dhclient => 'isc-dhcp-client',
|
||||
ifenslave => 'ifenslave',
|
||||
hwclock => 'util-linux-extra',
|
||||
);
|
||||
|
||||
my %mandatory = map { $_ => 1 } mandatory_commands($module);
|
||||
my @packages = required_packages($builder);
|
||||
|
||||
for my $command (sort keys %PACKAGE_FOR) {
|
||||
ok($mandatory{$command}, "the Ubuntu dracut module installs '$command' unconditionally");
|
||||
ok(scalar(grep { $_ eq $PACKAGE_FOR{$command} } @packages),
|
||||
"the build root installs $PACKAGE_FOR{$command}, which provides '$command'");
|
||||
}
|
||||
|
||||
# doxcat asks dhclient for the provisioning lease. An image without it never gets an address,
|
||||
# so the node netboots and never reports in -- the failure this test exists for.
|
||||
ok($mandatory{dhclient} && scalar(grep { $_ eq 'isc-dhcp-client' } @packages),
|
||||
'the Genesis image can obtain a DHCP lease');
|
||||
|
||||
# dracut_install is silent about a hole, so the payload needs its own gate before it is
|
||||
# packaged. This is the EL path's behaviour (xCAT-genesis-base.spec runs the same verifier).
|
||||
my $text = do { open my $fh, '<', $builder or die "$builder: $!"; local $/; <$fh> };
|
||||
like($text, qr{verify-genesis-payload}, 'builddeb-genesis-base verifies the payload it packages');
|
||||
|
||||
# mandatory_commands($module): source the dracut module with dracut_install shadowed, call
|
||||
# install(), and return the bare command names it installs unconditionally. Absolute paths are
|
||||
# data files, not commands, and are left out.
|
||||
sub mandatory_commands {
|
||||
my ($path) = @_;
|
||||
my $dir = tempdir(CLEANUP => 1);
|
||||
my $driver = "$dir/collect.sh";
|
||||
open my $fh, '>', $driver or die "$driver: $!";
|
||||
print $fh <<"BASH";
|
||||
dracut_install() { printf '%s\\n' "\$\@"; }
|
||||
instmods() { :; }
|
||||
inst_multiple() { :; }
|
||||
inst() { :; }
|
||||
dpkg-architecture() { echo x86_64-linux-gnu; }
|
||||
. '$path'
|
||||
# Every caller of _dracut_install_opt is optional by construction: it installs only what the
|
||||
# build root already has. Neutralise it AFTER sourcing so it cannot add to the mandatory set.
|
||||
_dracut_install_opt() { :; }
|
||||
install
|
||||
BASH
|
||||
close $fh;
|
||||
my @out = qx{bash '$driver' 2>/dev/null};
|
||||
BAIL_OUT("running install() from $path produced nothing") unless @out;
|
||||
my %seen;
|
||||
my @names = grep { !$seen{$_}++ } grep { length && !m{^/} } map { chomp; $_ } @out;
|
||||
BAIL_OUT("install() from $path named no bare commands") unless @names;
|
||||
return @names;
|
||||
}
|
||||
|
||||
# required_packages($path): extract the REQUIRED_PACKAGES assignment from the build script and
|
||||
# evaluate it, so the list comes from the value the script actually uses.
|
||||
sub required_packages {
|
||||
my ($path) = @_;
|
||||
my $text = do { open my $fh, '<', $path or die "$path: $!"; local $/; <$fh> };
|
||||
my ($block) = $text =~ /^(REQUIRED_PACKAGES="[^"]*")/ms;
|
||||
BAIL_OUT("no REQUIRED_PACKAGES assignment in $path") unless $block;
|
||||
my $out = qx{bash -c 'set -u; $block; printf "%s\\n" \$REQUIRED_PACKAGES' 2>/dev/null};
|
||||
my @packages = grep { length } split /\s+/, ($out // '');
|
||||
BAIL_OUT("REQUIRED_PACKAGES in $path evaluated to nothing") unless @packages;
|
||||
return @packages;
|
||||
}
|
||||
Reference in New Issue
Block a user