2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-08-27 17:16:40 +00:00

Merge pull request #7724 from VersatusHPC/feat/mknb-exported-genesis

feat(mknb): install exported Genesis images
This commit is contained in:
Daniel Hilst
2026-08-20 13:00:24 -03:00
committed by GitHub
6 changed files with 615 additions and 5 deletions
@@ -34,6 +34,8 @@ for the same architecture that the management node is. So you normally do not n
If you make custom changes to the network boot root image, you will need to run \ **mknb**\ again to regenerate the diskless image to include your changes. If you have an xCAT Hierarchical Cluster with Service Nodes having local /tftpboot directories (site.sharedtftp=0), you will need to copy the generated root image to each Service Node.
An exported Genesis image is identified by ``xcat-genesis.manifest``, which records its format version and architecture. The directory also contains ``kernel``, ``initramfs.cpio.gz``, and ``SHA256SUMS`` in ``/opt/xcat/share/xcat/netboot/genesis/ARCH``. \ **mknb**\ verifies the manifest and both boot files before publishing them under the configured TFTP root. An incomplete or invalid export leaves the current boot files unchanged. The legacy ``fs/`` layout remains supported and is packed locally.
When multiple IPv4 addresses are configured for the same network, \ **mknb**\ uses a locally assigned ``site.master`` for the xcatd endpoint, or the first address reported by the operating system when ``site.master`` is not local. POWER discovery configurations also use this address for their kernel and initrd URLs.
Presently, the architectures x86_64 and ppc64 are supported. For ppc64le, use the ppc64 architecture.
@@ -73,4 +75,3 @@ SEE ALSO
makedhcp(8)|makedhcp.8
+2 -1
View File
@@ -16,6 +16,8 @@ B<mknb> command yourself.
If you make custom changes to the network boot root image, you will need to run B<mknb> again to regenerate the diskless image to include your changes. If you have an xCAT Hierarchical Cluster with Service Nodes having local /tftpboot directories (site.sharedtftp=0), you will need to copy the generated root image to each Service Node.
An exported Genesis image is identified by C<xcat-genesis.manifest>, which records its format version and architecture. The directory also contains C<kernel>, C<initramfs.cpio.gz>, and C<SHA256SUMS> in C</opt/xcat/share/xcat/netboot/genesis/ARCH>. B<mknb> verifies the manifest and both boot files before publishing them under the configured TFTP root. An incomplete or invalid export leaves the current boot files unchanged. The legacy C<fs/> layout remains supported and is packed locally.
When multiple IPv4 addresses are configured for the same network, B<mknb> uses a locally assigned C<site.master> for the xcatd endpoint, or the first address reported by the operating system when C<site.master> is not local. POWER discovery configurations also use this address for their kernel and initrd URLs.
Presently, the architectures x86_64 and ppc64 are supported. For ppc64le, use the ppc64 architecture.
@@ -46,4 +48,3 @@ An error has occurred.
=head1 SEE ALSO
L<makedhcp(8)|makedhcp.8>
+232 -1
View File
@@ -1,5 +1,6 @@
package xCAT_plugin::mknb;
use strict;
use Digest::SHA ();
use File::Temp qw(tempdir);
use xCAT::Utils;
use xCAT::TableUtils;
@@ -7,6 +8,8 @@ use xCAT::NodeRange;
use File::Path;
use File::Copy;
my $GENESIS_EXPORT_MANIFEST = 'xcat-genesis.manifest';
sub handled_commands {
return {
mknb => 'mknb',
@@ -35,6 +38,214 @@ sub _select_network_addresses {
return (\%legacy, \%selected);
}
sub _genesis_export_manifest_present {
my ($directory) = @_;
my $manifest = "$directory/$GENESIS_EXPORT_MANIFEST";
return -e $manifest || -l $manifest;
}
sub _prebuilt_genesis_requested {
my ($directory) = @_;
foreach my $name (
$GENESIS_EXPORT_MANIFEST,
'kernel',
'initramfs.cpio.gz',
'SHA256SUMS'
)
{
my $path = "$directory/$name";
return 0 unless -e $path || -l $path;
}
return 1;
}
sub _validate_prebuilt_genesis_manifest {
my ($directory, $arch) = @_;
my $manifest = "$directory/$GENESIS_EXPORT_MANIFEST";
return "Missing Genesis export manifest: $manifest"
unless -f $manifest && !-l $manifest;
open(my $manifest_fh, '<:raw', $manifest)
or return "Unable to read Genesis export manifest: $manifest";
my %expected = (
format => 'xcat-genesis',
version => '1',
architecture => $arch,
);
my %values;
while (my $line = <$manifest_fh>) {
chomp($line);
unless ($line =~ /^([a-z][a-z0-9_-]*)=([A-Za-z0-9][A-Za-z0-9._+-]*)$/) {
close($manifest_fh);
return "Invalid Genesis export manifest entry: $line";
}
my ($name, $value) = ($1, $2);
unless (exists($expected{$name})) {
close($manifest_fh);
return "Unknown Genesis export manifest entry: $name";
}
if (exists($values{$name})) {
close($manifest_fh);
return "Duplicate Genesis export manifest entry: $name";
}
$values{$name} = $value;
}
close($manifest_fh);
foreach my $name (qw(format version architecture)) {
return "Missing Genesis export manifest entry: $name"
unless exists($values{$name});
return "Unsupported Genesis export $name: $values{$name}"
unless $values{$name} eq $expected{$name};
}
return;
}
sub _read_prebuilt_genesis_checksums {
my ($directory) = @_;
my $checksum_file = "$directory/SHA256SUMS";
return (undef, "Missing Genesis checksum file: $checksum_file")
unless -f $checksum_file && !-l $checksum_file;
open(my $checksum_fh, '<:raw', $checksum_file)
or return (undef, "Unable to read Genesis checksum file: $checksum_file");
my %expected;
while (my $line = <$checksum_fh>) {
chomp($line);
unless ($line =~ /^([0-9a-f]{64}) ([A-Za-z0-9][A-Za-z0-9._-]*)$/) {
close($checksum_fh);
return (undef, "Invalid Genesis checksum entry: $line");
}
my ($digest, $name) = ($1, $2);
if (exists($expected{$name})) {
close($checksum_fh);
return (undef, "Duplicate Genesis checksum entry: $name");
}
$expected{$name} = $digest;
}
close($checksum_fh);
return (\%expected, undef);
}
sub _sha256_file {
my ($path) = @_;
open(my $artifact_fh, '<:raw', $path)
or return (undef, "Unable to read Genesis artifact: $path");
my $digest = Digest::SHA->new(256)->addfile($artifact_fh)->hexdigest;
close($artifact_fh);
return ($digest, undef);
}
sub _install_prebuilt_genesis {
my ($source, $tftpdir, $arch) = @_;
return (undef, "Invalid Genesis export directory: $source")
unless -d $source && !-l $source;
my $manifest_error =
_validate_prebuilt_genesis_manifest($source, $arch);
return (undef, $manifest_error) if $manifest_error;
my ($expected, $checksum_error) =
_read_prebuilt_genesis_checksums($source);
return (undef, $checksum_error) if $checksum_error;
unless (exists($expected->{$GENESIS_EXPORT_MANIFEST})) {
return (undef,
"Missing Genesis checksum entry: $GENESIS_EXPORT_MANIFEST");
}
my ($manifest_digest, $manifest_digest_error) =
_sha256_file("$source/$GENESIS_EXPORT_MANIFEST");
return (undef, $manifest_digest_error) if $manifest_digest_error;
unless ($manifest_digest eq $expected->{$GENESIS_EXPORT_MANIFEST}) {
return (undef,
"Genesis checksum mismatch: $source/$GENESIS_EXPORT_MANIFEST");
}
my $destination_dir = "$tftpdir/xcat";
eval { mkpath($destination_dir) unless -d $destination_dir; };
return (undef, "Unable to create Genesis destination: $destination_dir")
unless -d $destination_dir;
my $suffix = xCAT::Utils::genpassword(24);
my @artifacts = (
[ 'kernel', "$destination_dir/genesis.kernel.$arch" ],
[ 'initramfs.cpio.gz', "$destination_dir/genesis.fs.$arch.gz" ],
);
my @staged;
foreach my $artifact (@artifacts) {
my ($name, $destination) = @{$artifact};
my $source_path = "$source/$name";
unless (-f $source_path && !-l $source_path) {
unlink(@staged);
return (undef, "Missing Genesis artifact: $source_path");
}
unless (exists($expected->{$name})) {
unlink(@staged);
return (undef, "Missing Genesis checksum entry: $name");
}
my $temporary = "$destination.$suffix.new";
unless (copy($source_path, $temporary) && chmod(0644, $temporary)) {
unlink(@staged, $temporary);
return (undef, "Unable to stage Genesis artifact: $source_path");
}
push(@staged, $temporary);
my ($digest, $digest_error) = _sha256_file($temporary);
if ($digest_error || $digest ne $expected->{$name}) {
unlink(@staged);
return (undef, $digest_error || "Genesis checksum mismatch: $source_path");
}
}
my %backups;
foreach my $artifact (@artifacts) {
my $destination = $artifact->[1];
next unless -e $destination || -l $destination;
unless (-f $destination && !-l $destination) {
unlink(@staged, values(%backups));
return (undef, "Invalid Genesis destination: $destination");
}
my $backup = "$destination.$suffix.old";
unless (copy($destination, $backup)) {
unlink(@staged, values(%backups));
return (undef, "Unable to preserve Genesis artifact: $destination");
}
$backups{$destination} = $backup;
}
my @installed;
foreach my $index (0 .. $#artifacts) {
my $destination = $artifacts[$index]->[1];
unless (rename($staged[$index], $destination)) {
my @rollback_errors;
foreach my $installed (reverse(@installed)) {
if ($backups{$installed}) {
push(@rollback_errors, $installed)
unless rename($backups{$installed}, $installed);
} else {
push(@rollback_errors, $installed) unless unlink($installed);
}
}
unlink(@staged[$index .. $#staged], values(%backups));
my $error = "Unable to install Genesis artifact: $destination";
$error .= "; unable to restore: " . join(', ', @rollback_errors)
if @rollback_errors;
return (undef, $error);
}
push(@installed, $destination);
}
unlink(values(%backups));
return ("$destination_dir/genesis.fs.$arch.gz", undef);
}
sub process_request {
my $request = shift;
my $callback = shift;
@@ -133,7 +344,8 @@ sub process_request {
$request->{arg}->[0] = $arch;
}
unless (-d "$::XCATROOT/share/xcat/netboot/$arch" or -d "$::XCATROOT/share/xcat/netboot/genesis/$arch") {
my $genesis_dir = "$::XCATROOT/share/xcat/netboot/genesis/$arch";
unless (-d "$::XCATROOT/share/xcat/netboot/$arch" or -d $genesis_dir) {
$callback->({ error => "Unable to find directory $::XCATROOT/share/xcat/netboot/$arch or $::XCATROOT/share/xcat/netboot/genesis/$arch", errorcode => [1] });
return;
}
@@ -144,6 +356,25 @@ sub process_request {
} elsif ($configfileonly) {
goto CREAT_CONF_FILE;
}
if (_prebuilt_genesis_requested($genesis_dir)) {
$callback->({ data => ["Installing exported Genesis image for $arch"] });
my ($installed_initrd, $install_error) =
_install_prebuilt_genesis($genesis_dir, $tftpdir, $arch);
if ($install_error) {
$callback->({ error => [$install_error], errorcode => [1] });
return;
}
$initrd_file = $installed_initrd;
$invisibletouch = 1;
goto CREAT_CONF_FILE;
}
if (_genesis_export_manifest_present($genesis_dir)) {
$callback->({
error => ["Incomplete Genesis export: $genesis_dir"],
errorcode => [1],
});
return;
}
# Grab all the standard ssh public keys we can
my @ssh_pub_keys = ();
if (-r "/root/.ssh/id_rsa.pub") {
+2 -2
View File
@@ -38,7 +38,7 @@ BuildArch: noarch
# Note: ifarch/ifnarch does not work for noarch package, use environment variable instead
%if %s390x
Requires: perl-IO-Socket-SSL perl-XML-Simple perl-XML-Parser
Requires: perl-IO-Socket-SSL perl-XML-Simple perl-XML-Parser perl(Digest::SHA)
%else
%if 0%{?rhel} >= 8
BuildRequires: perl-generators
@@ -46,7 +46,7 @@ BuildRequires: perl-generators
%if 0%{?fedora}
BuildRequires: perl-generators
%endif
Requires: perl-IO-Socket-SSL perl-XML-Simple perl-XML-Parser perl-Digest-SHA1 perl(LWP::Protocol::https) perl-XML-LibXML
Requires: perl-IO-Socket-SSL perl-XML-Simple perl-XML-Parser perl-Digest-SHA1 perl(Digest::SHA) perl(LWP::Protocol::https) perl-XML-LibXML
# AutoReqProv is off (above). Modules used only by xCAT-server code are
# therefore not auto-required, and (unlike SNMP, Expect, JSON, Net::Ping,
# Time::HiRes and Text::Balanced) are not pulled in transitively by perl-xCAT
+335
View File
@@ -0,0 +1,335 @@
#!/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 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');
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');
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 $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',
);
done_testing();
@@ -0,0 +1,42 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Spec;
use FindBin;
use Test::More;
my $repo_root = File::Spec->rel2abs(
File::Spec->catdir( $FindBin::Bin, '..', '..' )
);
sub read_file {
my ($filename) = @_;
open( my $fh, '<', $filename )
or die "Unable to read $filename: $!";
my $content = do { local $/; <$fh> };
close($fh);
return $content;
}
my $rpm_spec = read_file(
File::Spec->catfile( $repo_root, 'xCAT-server', 'xCAT-server.spec' )
);
my @rpm_sha_requirements =
$rpm_spec =~ /^Requires:.*\bperl\(Digest::SHA\)(?:\s|$)/mg;
is(
scalar(@rpm_sha_requirements),
2,
'both RPM architecture branches require Digest::SHA',
);
my $debian_control = read_file(
File::Spec->catfile( $repo_root, 'xCAT-server', 'debian', 'control' )
);
like(
$debian_control,
qr/^Depends:.*\blibdigest-sha-perl\b/m,
'the Debian package requires Digest::SHA',
);
done_testing();