2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-10-21 07:08:12 +00:00

test(statelite): cover the ramdisk archive that liteimg publishes

liteimg must take the image lock and keep the previous archive while it
packs and after a failed pack. It must publish the new archive with its
checksum, also when the check is turned off. It must change nothing
while the lock is held. With --tracker, it must name the given tracker
URL and describe the new archive under its final name.
This commit is contained in:
Vinícius Ferrão
2026-10-13 15:29:21 -03:00
parent 883549fea2
commit a4ac441a5d
+186
View File
@@ -0,0 +1,186 @@
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/../lib";
use lib "$FindBin::Bin/../../perl-xCAT";
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
use Cwd qw(getcwd realpath);
use Digest::SHA qw(sha256_hex);
use File::Path qw(make_path);
use File::Slurper qw(read_binary write_text);
use File::Temp qw(tempdir);
use Sys::Hostname qw(hostname);
use Test::More;
use XCAT::Test::File qw(repo_path);
plan skip_all => 'liteimg packs real archives with Linux tools' unless $^O eq 'linux';
local $ENV{PATH} = "$ENV{PATH}:/usr/sbin:/sbin";
sub tool {
my ($name) = @_;
my ($path) = grep { -x $_ } map { "$_/$name" } split /:/, $ENV{PATH};
return $path;
}
for my $name (qw(cpio gzip find)) {
plan skip_all => "$name is required" unless tool($name);
}
my $fixture = realpath(tempdir(CLEANUP => 1));
make_path("$fixture/db", "$fixture/root/lib/perl", "$fixture/root/share", "$fixture/install/postscripts");
symlink(repo_path('perl-xCAT/xCAT'), "$fixture/root/lib/perl/xCAT") or die $!;
symlink(repo_path('xCAT-server/share/xcat'), "$fixture/root/share/xcat") or die $!;
write_text("$fixture/install/postscripts/xcatdsklspost", "#!/bin/sh\nexit 0\n");
$ENV{XCATROOT} = "$fixture/root";
$ENV{XCATCFG} = "SQLite:$fixture/db";
require xCAT::Table;
require(repo_path('xCAT-server/lib/xcat/plugins/statelite.pm'));
xCAT::Table->new('site', -create => 1)->setAttribs({ key => 'installdir' }, { value => "$fixture/install" });
xCAT::Table->new('passwd', -create => 1)
->setAttribs({ key => 'system', username => 'root' }, { password => '*', cryptmethod => 'sha512' });
my $osimage = xCAT::Table->new('osimage', -create => 1);
my $linuximage = xCAT::Table->new('linuximage', -create => 1);
# The image lock lives in /var/lock/xcat, so the test records the request and decides whether another operation holds it.
our ($HELD, @LOCKED);
{
no warnings qw(redefine once);
*xCAT::Utils::acquire_lock_imageop = sub {
my (undef, $dir) = @_;
push @LOCKED, $dir;
return $HELD ? (1, "failed to acquire lock, seems there is another genimage/packimage/rmimage process 4242 running on root image dir \"$dir\"") : (0, {});
};
*xCAT::Utils::runcmd = sub {
my ($class, $command) = @_;
if ($command =~ /^ilitefile /) {
$::RUNCMD_RC = 0;
return ([ "$main::IMAGE tmpfs /etc/hosts" ]);
}
my @output = `$command 2>&1`;
$::RUNCMD_RC = $? >> 8;
return (\@output);
};
}
our $IMAGE;
(my $host = hostname()) =~ s/[^A-Za-z0-9.-]/_/g;
my $sequence = 0;
# The wrapper records what nodes would download while liteimg packs, then runs or fails the real compressor.
sub image {
my ($mode) = @_;
my $name = 'lite-' . ++$sequence;
my $dest = "$fixture/images/$name";
my $root = "$dest/rootimg";
my $archive = "$dest/rootimg-statelite.gz";
make_path("$root/etc/init.d", "$root/opt/xcat", "$root/usr/bin", "$dest/bin", "$dest/.packimage-$host+INTERRUP");
write_text("$root/etc/shadow", "root:*:1:0:99999:7:::\n");
write_text("$root/etc/fstab", "compute_x86_64 / tmpfs rw 0 1\n");
write_text("$root/etc/hosts", "127.0.0.1 localhost\n");
write_text("$root/usr/bin/payload", "payload of $name\n");
write_text($archive, "previous archive\n");
write_text("$archive.sha256", "previous checksum\n");
write_text("$dest/rootimg.cpio.gz", "stateless archive\n");
for my $compressor (qw(gzip pigz)) {
my $real = tool('gzip');
my $run = $mode eq 'fail' ? "printf 'injected failure\\n' >&2; exit 29" : "exec '$real' \"\$\@\"";
write_text("$dest/bin/$compressor", <<"SCRIPT");
#!/bin/sh
cat '$archive' > '$dest/seen'
$run
SCRIPT
chmod 0755, "$dest/bin/$compressor";
}
$osimage->setAttribs({ imagename => $name },
{ osvers => 'rhels9.6', osarch => 'x86_64', profile => 'compute', provmethod => 'statelite', rootfstype => 'ramdisk' });
$linuximage->setAttribs({ imagename => $name }, { rootimgdir => $dest });
return ($name, $dest, $archive);
}
sub liteimg {
my ($dest, $name, @args) = @_;
my ($cwd, $mask) = (getcwd(), umask());
local $IMAGE = $name;
@LOCKED = ();
my @responses;
my $output = '';
open(my $stdout, '>', \$output) or die $!;
{
local $ENV{PATH} = "$dest/bin:$ENV{PATH}";
local *STDOUT = $stdout;
eval { xCAT_plugin::statelite::process_request({ arg => [ @args, $name ] }, sub { push @responses, @_ }) };
push @responses, { error => ["liteimg died: $@"] } if $@;
}
chdir($cwd) or die $!;
umask($mask);
return join("\n", map { @{ $_->{error} || [] } } @responses);
}
sub listing {
my ($archive) = @_;
open(my $pipe, '-|', '/bin/sh', '-c', 'gzip -dc "$1" | cpio -t 2>/dev/null', 'list', $archive) or die $!;
my $text = do { local $/; <$pipe> };
close($pipe);
return $text;
}
{
my ($name, $dest, $archive) = image('pass');
my $errors = liteimg($dest, $name);
is($errors, '', 'liteimg reports no error');
is_deeply(\@LOCKED, ["$dest/rootimg"], 'liteimg holds the image lock that packimage takes');
is(read_binary("$dest/seen"), "previous archive\n", 'nodes get the previous archive while liteimg packs');
like(listing($archive), qr{(?:^|/)usr/bin/payload$}m, 'the new archive holds the image');
is((stat($archive))[2] & 07777, 0644, 'the new archive is readable by nodes');
is(read_binary("$archive.sha256"), sha256_hex(read_binary($archive)) . " rootimg-statelite.gz\n",
'the checksum of the new archive is published next to it');
is(read_binary("$dest/rootimg.cpio.gz"), "stateless archive\n", 'the stateless archive of the image stays');
is_deeply([ glob("$dest/.packimage-$host+*") ], [], 'no temporary directory is left, including an abandoned one');
ok(!-e "$dest/.packimage-publish", 'the publication lock is released');
}
{
my ($name, $dest, $archive) = image('fail');
my $errors = liteimg($dest, $name);
like($errors, qr/liteimg failed while running: .*(?:pigz|gzip)/, 'a failed compressor is reported');
is(read_binary($archive), "previous archive\n", 'a failed pack keeps the previous archive');
is(read_binary("$archive.sha256"), "previous checksum\n", 'a failed pack keeps the previous checksum');
is_deeply([ glob("$dest/.packimage-$host+*") ], [], 'a failed pack leaves no partial archive');
}
{
my ($name, $dest, $archive) = image('pass');
$linuximage->setAttribs({ imagename => $name }, { rootimgchecksum => 'no' });
my $errors = liteimg($dest, $name);
is($errors, '', 'liteimg with the check turned off reports no error');
like(listing($archive), qr{(?:^|/)usr/bin/payload$}m, 'the new archive is published');
is(read_binary("$archive.sha256"), sha256_hex(read_binary($archive)) . " rootimg-statelite.gz\n",
'the checksum is published also when the check is turned off');
}
{
local $HELD = 1;
my ($name, $dest, $archive) = image('pass');
my $errors = liteimg($dest, $name);
like($errors, qr/failed to acquire lock/, 'liteimg fails while another image operation holds the lock');
is(read_binary($archive), "previous archive\n", 'the published archive stays while the lock is held');
ok(!-e "$dest/rootimg/xcatpost", 'liteimg does not change the image while the lock is held');
}
{
my ($name, $dest, $archive) = image('pass');
write_text("$dest/bin/ctorrent", <<"SCRIPT");
#!/bin/sh
echo "\$*" > '$dest/ctorrent.args'
while [ \$# -gt 1 ]; do [ "\$1" = -s ] && out=\$2; shift; done
{ echo "\$1 \$(stat -c %s "\$1")"; cat '$archive'; } > "\$out"
SCRIPT
chmod 0755, "$dest/bin/ctorrent";
my $errors = liteimg($dest, $name, '--tracker', 'http://192.0.2.1:6969/announce');
is($errors, '', 'liteimg with --tracker reports no error');
like(read_binary("$dest/ctorrent.args"), qr{(?:^| )-u http://192\.0\.2\.1:6969/announce }, 'the metainfo names the tracker URL that --tracker gives');
is(read_binary("$archive.metainfo"), 'rootimg-statelite.gz ' . (-s $archive) . "\nprevious archive\n",
'the metainfo describes the new archive under its final name before the archive is replaced');
}
done_testing();