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:
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user