diff --git a/xCAT-test/unit/mknb_exported_genesis.t b/xCAT-test/unit/mknb_exported_genesis.t new file mode 100644 index 000000000..b4845476e --- /dev/null +++ b/xCAT-test/unit/mknb_exported_genesis.t @@ -0,0 +1,221 @@ +#!/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); +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 prepare_export { + my ($directory, $kernel, $initramfs) = @_; + make_path($directory); + write_file("$directory/kernel", $kernel); + write_file("$directory/initramfs.cpio.gz", $initramfs); + write_file( + "$directory/SHA256SUMS", + sha256_hex($initramfs) . " initramfs.cpio.gz\n" + . sha256_hex($kernel) . " kernel\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'); + +ok( + xCAT_plugin::mknb::_prebuilt_genesis_requested($export), + 'an exported initramfs 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'); +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($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 $partial_export = "$tmpdir/partial"; +make_path($partial_export); +write_file("$partial_export/initramfs.cpio.gz", 'partial initramfs'); +ok( + xCAT_plugin::mknb::_prebuilt_genesis_requested($partial_export), + 'a partial export still selects the prebuilt path', +); +(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_entry = "$tmpdir/missing-entry"; +prepare_export($missing_entry, 'kernel', 'initramfs'); +write_file( + "$missing_entry/SHA256SUMS", + sha256_hex('kernel') . " kernel\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'); +$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', +); + +done_testing();