2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-09-09 14:36:43 +00:00
Files
xcat-core/xCAT-test/unit/mknb_exported_genesis.t
T
2026-08-25 11:27:13 -03:00

518 lines
20 KiB
Perl

#!/usr/bin/env perl
use strict;
use warnings;
## no critic (Modules::RequireFilenameMatchesPackage, TestingAndDebugging::ProhibitNoStrict, TestingAndDebugging::ProhibitNoWarnings)
use Digest::SHA qw(sha256_hex);
use File::Path qw(make_path remove_tree);
use File::Temp qw(tempdir);
use FindBin;
use Test::More;
BEGIN {
package xCAT::Utils;
sub genpassword { return 'test-token'; }
$INC{'xCAT/Utils.pm'} = __FILE__;
package xCAT::TableUtils;
our ($tftpdir, $site_master);
sub getTftpDir { return $tftpdir; }
sub get_site_attribute {
my $attribute = $_[-1];
return ($site_master) if $attribute eq 'master' && defined($site_master);
return;
}
$INC{'xCAT/TableUtils.pm'} = __FILE__;
package xCAT::NetworkUtils;
our ($normnet_addresses, $hexnet_addresses, @master_addresses);
sub my_nets { return $normnet_addresses; }
sub my_hexnets { return $hexnet_addresses; }
sub getipaddr { return @master_addresses; }
$INC{'xCAT/NetworkUtils.pm'} = __FILE__;
package xCAT::NodeRange;
sub import {
my $caller = caller;
no strict 'refs';
*{"${caller}::noderange"} = sub { return; };
}
$INC{'xCAT/NodeRange.pm'} = __FILE__;
}
my $source_mknb_plugin =
"$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/mknb.pm";
if (-f $source_mknb_plugin) {
require $source_mknb_plugin;
} else {
require xCAT_plugin::mknb;
}
sub write_file {
my ($filename, $content) = @_;
open(my $fh, '>:raw', $filename) or die "Unable to write $filename: $!";
print {$fh} $content;
close($fh) or die "Unable to close $filename: $!";
}
sub read_file {
my ($filename) = @_;
open(my $fh, '<:raw', $filename) or die "Unable to read $filename: $!";
my $content = do { local $/; <$fh> };
close($fh);
return $content;
}
sub export_manifest {
my ($architecture) = @_;
return "format=xcat-genesis\n"
. "version=1\n"
. "architecture=$architecture\n";
}
sub prepare_export {
my ($directory, $kernel, $initramfs, $architecture) = @_;
$architecture //= 'x86_64';
make_path($directory);
write_file("$directory/kernel", $kernel);
write_file("$directory/initramfs.cpio.gz", $initramfs);
my $manifest = export_manifest($architecture);
write_file("$directory/xcat-genesis.manifest", $manifest);
write_file(
"$directory/SHA256SUMS",
sha256_hex($initramfs) . " initramfs.cpio.gz\n"
. sha256_hex($kernel) . " kernel\n"
. sha256_hex($manifest) . " xcat-genesis.manifest\n",
);
}
sub published_files {
my ($tftpdir, $arch) = @_;
return (
"$tftpdir/xcat/genesis.kernel.$arch",
"$tftpdir/xcat/genesis.fs.$arch.gz",
);
}
sub temporary_files {
my ($directory) = @_;
opendir(my $dir_fh, $directory) or return;
my @files = grep { /\.test-token\.(?:new|old)$/ } readdir($dir_fh);
closedir($dir_fh);
return @files;
}
my $tmpdir = tempdir(CLEANUP => 1);
my $export = "$tmpdir/export";
my $tftpdir = "$tmpdir/tftpboot";
prepare_export($export, 'new kernel', 'new initramfs');
make_path("$tftpdir/xcat");
write_file("$tftpdir/xcat/genesis.fs.x86_64.lzma", 'old initramfs');
ok(
xCAT_plugin::mknb::_prebuilt_genesis_requested($export),
'an export manifest selects the prebuilt path',
);
my ($installed_initrd, $install_error) =
xCAT_plugin::mknb::_install_prebuilt_genesis($export, $tftpdir, 'x86_64');
is($install_error, undef, 'a valid export installs without an error');
is(
$installed_initrd,
"$tftpdir/xcat/genesis.fs.x86_64.gz",
'the installer returns the published initramfs path',
);
my ($published_kernel, $published_initramfs) =
published_files($tftpdir, 'x86_64');
is(read_file($published_kernel), 'new kernel', 'the kernel is published');
is(read_file($published_initramfs), 'new initramfs', 'the initramfs is published');
ok(!-e "$tftpdir/xcat/genesis.fs.x86_64.lzma",
'installing an export removes the obsolete legacy initramfs');
is(
read_file("$tftpdir/xcat/genesis.exact-arch.x86_64"),
export_manifest('x86_64'),
'the canonical architecture marker is published with the image',
);
is(sprintf('%04o', (stat($published_kernel))[2] & oct('7777')), '0644', 'the kernel is readable by TFTP');
is(sprintf('%04o', (stat($published_initramfs))[2] & oct('7777')), '0644', 'the initramfs is readable by TFTP');
is_deeply([temporary_files("$tftpdir/xcat")], [], 'staging files are removed');
write_file("$tftpdir/xcat/genesis.kernel.ppc64le", 'stale kernel');
write_file("$tftpdir/xcat/genesis.fs.ppc64le.gz", 'stale initramfs');
write_file("$tftpdir/xcat/genesis.fs.ppc64le.lzma", 'stale legacy initramfs');
write_file("$tftpdir/xcat/genesis.exact-arch.ppc64le", export_manifest('ppc64le'));
write_file("$tftpdir/xcat/genesis.kernel.ppc64", 'legacy kernel');
my ($removed, $remove_error) =
xCAT_plugin::mknb::_remove_openembedded_genesis($tftpdir, 'ppc64le');
is($remove_error, undef, 'OpenEmbedded boot artifacts can be retired cleanly');
is($removed, 4, 'all exact-architecture boot artifacts are retired');
ok(!-e "$tftpdir/xcat/genesis.kernel.ppc64le",
'the exact OpenEmbedded kernel is removed');
ok(!-e "$tftpdir/xcat/genesis.fs.ppc64le.gz"
&& !-e "$tftpdir/xcat/genesis.fs.ppc64le.lzma",
'the exact OpenEmbedded initramfs files are removed');
ok(!-e "$tftpdir/xcat/genesis.exact-arch.ppc64le",
'the canonical architecture marker is removed');
ok(-f "$tftpdir/xcat/genesis.kernel.ppc64",
'retiring ppc64le does not remove the legacy ppc64 fallback');
my $failed_kernel = "$tftpdir/xcat/genesis.kernel.aarch64";
make_path($failed_kernel);
for my $artifact (qw(
genesis.fs.aarch64.gz
genesis.fs.aarch64.lzma
genesis.exact-arch.aarch64
)) {
write_file("$tftpdir/xcat/$artifact", 'stale artifact');
}
($removed, $remove_error) =
xCAT_plugin::mknb::_remove_openembedded_genesis($tftpdir, 'aarch64');
is($removed, 3, 'artifact cleanup continues after an unlink failure');
like(
$remove_error,
qr/Unable to remove Genesis artifact: \Q$failed_kernel\E/,
'artifact cleanup reports the failed path',
);
ok(-d $failed_kernel, 'the failed artifact remains in place');
ok(
!-e "$tftpdir/xcat/genesis.fs.aarch64.gz"
&& !-e "$tftpdir/xcat/genesis.fs.aarch64.lzma"
&& !-e "$tftpdir/xcat/genesis.exact-arch.aarch64",
'artifact cleanup removes every remaining path',
);
write_file($published_kernel, 'current kernel');
write_file($published_initramfs, 'current initramfs');
write_file("$export/kernel", 'corrupt kernel');
($installed_initrd, $install_error) =
xCAT_plugin::mknb::_install_prebuilt_genesis($export, $tftpdir, 'x86_64');
like($install_error, qr/Genesis checksum mismatch/, 'a checksum mismatch is rejected');
is(read_file($published_kernel), 'current kernel', 'a bad export keeps the current kernel');
is(read_file($published_initramfs), 'current initramfs', 'a bad export keeps the current initramfs');
is_deeply([temporary_files("$tftpdir/xcat")], [], 'a failed install removes staging files');
my $unmarked_export = "$tmpdir/unmarked";
prepare_export($unmarked_export, 'kernel', 'initramfs');
unlink("$unmarked_export/xcat-genesis.manifest");
ok(
!xCAT_plugin::mknb::_prebuilt_genesis_requested($unmarked_export),
'boot artifacts without an export manifest do not select the prebuilt path',
);
(undef, $install_error) = xCAT_plugin::mknb::_install_prebuilt_genesis(
$unmarked_export, $tftpdir, 'x86_64'
);
like($install_error, qr/Missing Genesis export manifest/, 'the installer requires the export manifest');
my $partial_export = "$tmpdir/partial";
make_path($partial_export);
write_file("$partial_export/initramfs.cpio.gz", 'partial initramfs');
write_file(
"$partial_export/xcat-genesis.manifest",
export_manifest('x86_64'),
);
ok(
!xCAT_plugin::mknb::_prebuilt_genesis_requested($partial_export),
'a partial export does not satisfy the prebuilt layout',
);
ok(
xCAT_plugin::mknb::_genesis_export_manifest_present($partial_export),
'a partial export still declares the new format',
);
(undef, $install_error) = xCAT_plugin::mknb::_install_prebuilt_genesis(
$partial_export, $tftpdir, 'x86_64'
);
like($install_error, qr/Missing Genesis checksum file/, 'a partial export fails closed');
my $missing_manifest_checksum = "$tmpdir/missing-manifest-checksum";
prepare_export($missing_manifest_checksum, 'kernel', 'initramfs');
write_file(
"$missing_manifest_checksum/SHA256SUMS",
sha256_hex('kernel') . " kernel\n"
. sha256_hex('initramfs') . " initramfs.cpio.gz\n",
);
(undef, $install_error) = xCAT_plugin::mknb::_install_prebuilt_genesis(
$missing_manifest_checksum, $tftpdir, 'x86_64'
);
like($install_error, qr/Missing Genesis checksum entry: xcat-genesis\.manifest/, 'the export manifest needs a checksum');
my $bad_manifest_checksum = "$tmpdir/bad-manifest-checksum";
prepare_export($bad_manifest_checksum, 'kernel', 'initramfs');
write_file(
"$bad_manifest_checksum/xcat-genesis.manifest",
"architecture=x86_64\nversion=1\nformat=xcat-genesis\n",
);
(undef, $install_error) = xCAT_plugin::mknb::_install_prebuilt_genesis(
$bad_manifest_checksum, $tftpdir, 'x86_64'
);
like($install_error, qr/Genesis checksum mismatch: .*xcat-genesis\.manifest/, 'the export manifest checksum is verified');
my @invalid_manifests = (
[ malformed => "not a manifest\n", qr/Invalid Genesis export manifest entry/ ],
[ unknown => export_manifest('x86_64') . "label=test\n", qr/Unknown Genesis export manifest entry: label/ ],
[ duplicate => export_manifest('x86_64') . "version=1\n", qr/Duplicate Genesis export manifest entry: version/ ],
[ missing => "format=xcat-genesis\nversion=1\n", qr/Missing Genesis export manifest entry: architecture/ ],
[ version => "format=xcat-genesis\nversion=2\narchitecture=x86_64\n", qr/Unsupported Genesis export version: 2/ ],
[ architecture => export_manifest('ppc64le'), qr/Unsupported Genesis export architecture: ppc64le/ ],
);
foreach my $case (@invalid_manifests) {
my ($name, $content, $error_pattern) = @{$case};
my $invalid_export = "$tmpdir/manifest-$name";
prepare_export($invalid_export, 'kernel', 'initramfs');
write_file("$invalid_export/xcat-genesis.manifest", $content);
(undef, $install_error) = xCAT_plugin::mknb::_install_prebuilt_genesis(
$invalid_export, $tftpdir, 'x86_64'
);
like($install_error, $error_pattern, "$name export manifests are rejected");
}
my $symlink_manifest = "$tmpdir/symlink-manifest";
prepare_export($symlink_manifest, 'kernel', 'initramfs');
unlink("$symlink_manifest/xcat-genesis.manifest");
symlink("$symlink_manifest/kernel", "$symlink_manifest/xcat-genesis.manifest")
or die "Unable to create manifest symlink: $!";
ok(
xCAT_plugin::mknb::_prebuilt_genesis_requested($symlink_manifest),
'a manifest symlink still selects the prebuilt path',
);
(undef, $install_error) = xCAT_plugin::mknb::_install_prebuilt_genesis(
$symlink_manifest, $tftpdir, 'x86_64'
);
like($install_error, qr/Missing Genesis export manifest/, 'export manifest symlinks fail closed');
my $missing_entry = "$tmpdir/missing-entry";
prepare_export($missing_entry, 'kernel', 'initramfs');
write_file(
"$missing_entry/SHA256SUMS",
sha256_hex('kernel') . " kernel\n"
. sha256_hex(export_manifest('x86_64'))
. " xcat-genesis.manifest\n",
);
(undef, $install_error) = xCAT_plugin::mknb::_install_prebuilt_genesis(
$missing_entry, $tftpdir, 'x86_64'
);
like($install_error, qr/Missing Genesis checksum entry: initramfs\.cpio\.gz/, 'each published file needs a checksum');
my $duplicate_entry = "$tmpdir/duplicate-entry";
prepare_export($duplicate_entry, 'kernel', 'initramfs');
write_file(
"$duplicate_entry/SHA256SUMS",
sha256_hex('kernel') . " kernel\n"
. sha256_hex('kernel') . " kernel\n",
);
(undef, $install_error) = xCAT_plugin::mknb::_install_prebuilt_genesis(
$duplicate_entry, $tftpdir, 'x86_64'
);
like($install_error, qr/Duplicate Genesis checksum entry: kernel/, 'duplicate checksum entries are rejected');
my $malformed_entry = "$tmpdir/malformed-entry";
prepare_export($malformed_entry, 'kernel', 'initramfs');
write_file("$malformed_entry/SHA256SUMS", "not a checksum\n");
(undef, $install_error) = xCAT_plugin::mknb::_install_prebuilt_genesis(
$malformed_entry, $tftpdir, 'x86_64'
);
like($install_error, qr/Invalid Genesis checksum entry/, 'malformed checksum entries are rejected');
my $symlink_export = "$tmpdir/symlink-export";
prepare_export($symlink_export, 'kernel', 'initramfs');
unlink("$symlink_export/kernel");
symlink("$symlink_export/initramfs.cpio.gz", "$symlink_export/kernel")
or die "Unable to create test symlink: $!";
(undef, $install_error) = xCAT_plugin::mknb::_install_prebuilt_genesis(
$symlink_export, $tftpdir, 'x86_64'
);
like($install_error, qr/Missing Genesis artifact: .*\/kernel/, 'artifact symlinks are rejected');
my $legacy_directory = "$tmpdir/legacy";
make_path("$legacy_directory/fs");
write_file("$legacy_directory/kernel", 'legacy kernel');
ok(
!xCAT_plugin::mknb::_prebuilt_genesis_requested($legacy_directory),
'the legacy fs layout does not select the prebuilt path',
);
$::XCATROOT = "$tmpdir/xcatroot";
my $process_export =
"$::XCATROOT/share/xcat/netboot/genesis/ppc64";
prepare_export($process_export, 'process kernel', 'process initramfs', 'ppc64');
$xCAT::TableUtils::tftpdir = "$tmpdir/custom-tftpboot";
$xCAT::NetworkUtils::normnet_addresses = {
'192.0.2.0/24' => ['192.0.2.1'],
};
$xCAT::NetworkUtils::hexnet_addresses = {};
$xCAT::TableUtils::site_master = undef;
@xCAT::NetworkUtils::master_addresses = ();
my @responses;
xCAT_plugin::mknb::process_request(
{ arg => ['ppc64'] },
sub { push(@responses, @_); },
);
ok(
!grep({ ref($_) eq 'HASH' && $_->{error} } @responses),
'mknb accepts the exported layout',
);
my ($process_kernel, $process_initramfs) =
published_files($xCAT::TableUtils::tftpdir, 'ppc64');
is(read_file($process_kernel), 'process kernel', 'mknb publishes the exported kernel');
is(read_file($process_initramfs), 'process initramfs', 'mknb publishes the exported initramfs');
ok(
-f "$xCAT::TableUtils::tftpdir/pxelinux.cfg/p/192.0.2.0_24",
'mknb writes boot configuration after publishing the export',
);
unlink("$process_export/SHA256SUMS");
@responses = ();
xCAT_plugin::mknb::process_request(
{ arg => ['ppc64'] },
sub { push(@responses, @_); },
);
ok(
grep(
{ ref($_) eq 'HASH' && $_->{error}
&& $_->{error}->[0] =~ /Incomplete Genesis export/ }
@responses
),
'mknb rejects an incomplete marked export',
);
is(
read_file($process_kernel),
'process kernel',
'an incomplete marked export keeps the published kernel',
);
is(
read_file($process_initramfs),
'process initramfs',
'an incomplete marked export keeps the published initramfs',
);
$::XCATROOT = "$tmpdir/openembedded-xcatroot";
my $openembedded_process_export =
"$::XCATROOT/share/xcat/netboot/genesis-openembedded/ppc64le";
my $legacy_process_export =
"$::XCATROOT/share/xcat/netboot/genesis/ppc64";
prepare_export(
$openembedded_process_export,
'openembedded ppc64le kernel',
'openembedded ppc64le initramfs',
'ppc64le',
);
prepare_export(
$legacy_process_export,
'legacy ppc64 kernel',
'legacy ppc64 initramfs',
'ppc64',
);
$xCAT::TableUtils::tftpdir = "$tmpdir/openembedded-tftpboot";
@responses = ();
xCAT_plugin::mknb::process_request(
{ arg => ['ppc64le'] },
sub { push(@responses, @_); },
);
ok(
!grep({ ref($_) eq 'HASH' && $_->{error} } @responses),
'mknb installs an exact ppc64le OpenEmbedded export',
);
my ($openembedded_kernel, $openembedded_initramfs) =
published_files($xCAT::TableUtils::tftpdir, 'ppc64le');
is(read_file($openembedded_kernel), 'openembedded ppc64le kernel',
'mknb prefers the OpenEmbedded kernel when legacy Genesis is also installed');
is(read_file($openembedded_initramfs), 'openembedded ppc64le initramfs',
'mknb prefers the OpenEmbedded initramfs when legacy Genesis is also installed');
my $ppc64le_config = read_file(
"$xCAT::TableUtils::tftpdir/pxelinux.cfg/p/192.0.2.0_24"
);
like($ppc64le_config, qr/genesis\.kernel\.ppc64le/,
'POWER boot configuration keeps the exact ppc64le artifact name');
like($ppc64le_config, qr/genesis\.fs\.ppc64le\.gz/,
'POWER boot configuration uses the exact ppc64le initramfs name');
remove_tree($openembedded_process_export);
write_file(
"$xCAT::TableUtils::tftpdir/xcat/genesis.kernel.ppc64",
'canonical big-endian ppc64 kernel',
);
write_file(
"$xCAT::TableUtils::tftpdir/xcat/genesis.exact-arch.ppc64",
export_manifest('ppc64'),
);
@responses = ();
xCAT_plugin::mknb::process_request(
{ arg => ['ppc64le'] },
sub { push(@responses, @_); },
);
ok(
grep(
{ ref($_) eq 'HASH' && $_->{error}
&& $_->{error} =~ /canonical ppc64 image/ }
@responses
),
'the legacy ppc64le fallback cannot replace a canonical ppc64 image',
);
is(
read_file("$xCAT::TableUtils::tftpdir/xcat/genesis.kernel.ppc64"),
'canonical big-endian ppc64 kernel',
'a refused ppc64le fallback leaves the canonical ppc64 kernel intact',
);
my $selection_root = "$tmpdir/selection-root";
my $legacy_x86 = "$selection_root/share/xcat/netboot/genesis/x86_64";
my $openembedded_x86 =
"$selection_root/share/xcat/netboot/genesis-openembedded/x86_64";
make_path("$legacy_x86/fs", $openembedded_x86);
my ($selected_dir, $selected_arch, $selected_type) =
xCAT_plugin::mknb::_select_genesis_source($selection_root, 'x86_64');
is($selected_dir, $openembedded_x86,
'an installed OpenEmbedded export takes precedence over legacy Genesis');
is($selected_arch, 'x86_64', 'the OpenEmbedded x86_64 name is unchanged');
is($selected_type, 'openembedded', 'the selected source is identified');
remove_tree($openembedded_x86);
($selected_dir, $selected_arch, $selected_type) =
xCAT_plugin::mknb::_select_genesis_source($selection_root, 'x86_64');
is($selected_dir, $legacy_x86, 'legacy Genesis remains the fallback');
is($selected_arch, 'x86_64', 'the legacy x86_64 name is unchanged');
is($selected_type, 'legacy', 'the fallback source is identified');
my $linked_legacy = "$tmpdir/linked-legacy-x86_64";
make_path("$linked_legacy/fs");
remove_tree($legacy_x86);
symlink($linked_legacy, $legacy_x86)
or die "Unable to create legacy Genesis directory symlink: $!";
($selected_dir, $selected_arch, $selected_type) =
xCAT_plugin::mknb::_select_genesis_source($selection_root, 'x86_64');
is($selected_dir, $legacy_x86, 'legacy Genesis directory symlinks remain usable');
is($selected_type, 'legacy', 'a linked legacy source keeps its source type');
my $openembedded_ppc64le =
"$selection_root/share/xcat/netboot/genesis-openembedded/ppc64le";
my $legacy_ppc64 = "$selection_root/share/xcat/netboot/genesis/ppc64";
make_path($openembedded_ppc64le, "$legacy_ppc64/fs");
($selected_dir, $selected_arch, $selected_type) =
xCAT_plugin::mknb::_select_genesis_source($selection_root, 'ppc64le');
is($selected_dir, $openembedded_ppc64le,
'ppc64le selects its exact OpenEmbedded export');
is($selected_arch, 'ppc64le', 'ppc64le is not rewritten to ppc64');
is($selected_type, 'openembedded', 'ppc64le uses the OpenEmbedded source');
($selected_dir, $selected_arch, $selected_type) =
xCAT_plugin::mknb::_select_genesis_source($selection_root, 'ppc64el');
is($selected_dir, $openembedded_ppc64le,
'the Debian spelling resolves to the canonical ppc64le export');
is($selected_arch, 'ppc64le', 'the canonical architecture name is returned');
remove_tree($openembedded_ppc64le);
($selected_dir, $selected_arch, $selected_type) =
xCAT_plugin::mknb::_select_genesis_source($selection_root, 'ppc64le');
is($selected_dir, $legacy_ppc64,
'ppc64le falls back to the old combined POWER image when needed');
is($selected_arch, 'ppc64', 'only the legacy fallback uses the old ppc64 name');
is($selected_type, 'legacy', 'the POWER fallback is identified as legacy');
my $unsafe = xCAT_plugin::mknb::_select_genesis_source(
$selection_root, '../../outside'
);
is($unsafe, undef, 'unsupported architecture names cannot escape the image root');
done_testing();