2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-10-02 08:51:44 +00:00

test: separate native openEuler checks from unit tests

This commit is contained in:
Vinícius Ferrão
2026-09-22 23:15:42 -03:00
parent 16b6acf620
commit f49941fb62
32 changed files with 569 additions and 524 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Install dependencies
run: sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends --no-install-suggests bats build-essential fakeroot reprepro devscripts debhelper libcapture-tiny-perl libfile-slurper-perl libjson-perl libparallel-forkmanager-perl libsoap-lite-perl libdbi-perl libcgi-pm-perl quilt openssh-server dpkg looptools genometools software-properties-common
run: sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends --no-install-suggests bats rpm build-essential fakeroot reprepro devscripts debhelper libcapture-tiny-perl libfile-slurper-perl libjson-perl libparallel-forkmanager-perl libsoap-lite-perl libdbi-perl libcgi-pm-perl quilt openssh-server dpkg looptools genometools software-properties-common
- name: Run tests
run: perl github_action_xcat_test.pl
+162
View File
@@ -0,0 +1,162 @@
#!/usr/bin/env bats
load helpers/go_xcat
setup() {
go_xcat_require_source
source "$GO_XCAT_SOURCE"
FIXTURE="$BATS_TEST_TMPDIR/repos"
mkdir -p "$FIXTURE"
TMP_DIR="$FIXTURE"
GO_XCAT_DEFAULT_BASE_URL=https://packages.example.test/xcat
GO_XCAT_ARCH=x86_64
TEST_ID=openEuler
TEST_VERSION='24.03 (LTS-SP3)'
TEST_VERSION_ID=24.03
PACKAGE_STATUS=0
os_release_value() {
case "$1" in
ID) printf '%s\n' "$TEST_ID" ;;
VERSION) printf '%s\n' "$TEST_VERSION" ;;
VERSION_ID) printf '%s\n' "$TEST_VERSION_ID" ;;
*) return 64 ;;
esac
}
dnf() {
if [[ $1 == repoquery ]]; then printf '%s\n' "${@: -1}"
else printf 'dnf'; printf ' <%s>' "$@"; printf '\n'; fi
return "$PACKAGE_STATUS"
}
yum() { printf 'yum'; printf ' <%s>' "$@"; printf '\n'; return "$PACKAGE_STATUS"; }
download_file() { return 1; }
add_repo_by_file() { cp "$1" "$FIXTURE/$2.repo"; }
select_platform
}
select_platform() {
GO_XCAT_LINUX_DISTRO="$(check_linux_distro)"
GO_XCAT_LINUX_VERSION="$(check_linux_version)"
}
repositories() {
select_platform || return
add_xcat_core_repo_yum_or_zypper "${CORE_URL:-}" "${REPO_VERSION:-latest}" || return
add_xcat_dep_repo_yum_or_zypper "${DEP_URL:-}" "${REPO_VERSION:-latest}"
}
@test "native repositories retain each release, service pack and architecture" {
while IFS='|' read -r TEST_VERSION expected; do
for GO_XCAT_ARCH in x86_64 ppc64le; do
run repositories
[ "$status" -eq 0 ]
grep -Fx "baseurl=https://packages.example.test/xcat/yum/latest/xcat-core/openeuler$expected/$GO_XCAT_ARCH" "$FIXTURE/xcat-core.repo"
grep -Fx "baseurl=https://packages.example.test/xcat/yum/latest/xcat-dep/openeuler$expected/$GO_XCAT_ARCH" "$FIXTURE/xcat-dep.repo"
done
done <<'CASES'
20.03 (LTS-SP4)|20.03sp4
22.03 (LTS-SP4)|22.03sp4
24.03 (LTS-SP1)|24.03sp1
24.03 (LTS-SP3)|24.03sp3
24.03 (LTS-SP4)|24.03sp4
24.03 (LTS)|24.03
CASES
}
@test "native identity and GA fallback" {
for TEST_ID in openEuler openeuler "'openEuler'"; do
run check_linux_distro
[ "$status" -eq 0 ]
[ "$output" = openeuler ]
done
TEST_VERSION="'24.03 (LTS-SP3)'"
run check_linux_version
[ "$status" -eq 0 ]
[ "$output" = 24.03sp3 ]
TEST_VERSION=''
run check_linux_version
[ "$status" -eq 0 ]
[ "$output" = 24.03 ]
}
@test "invalid releases and architectures create no repositories" {
for TEST_VERSION in 24.09 '25.03 (LTS)' '24.03 (LTS-SP0)' '24.03 (LTS-SP04)' '24.03 (LTS-SP3) trailing'; do
run repositories
[ "$status" -ne 0 ]
[ ! -e "$FIXTURE/xcat-core.repo" ]
done
TEST_VERSION='24.03 (LTS-SP3)'
GO_XCAT_ARCH=ppc64
run repositories
[ "$status" -ne 0 ]
[ ! -e "$FIXTURE/xcat-core.repo" ]
}
@test "explicit URLs and development paths retain native layout" {
CORE_URL=https://custom.example.test/native-core
DEP_URL=https://custom.example.test/native-dep
run repositories
[ "$status" -eq 0 ]
grep -Fx "baseurl=$CORE_URL" "$FIXTURE/xcat-core.repo"
grep -Fx "baseurl=$DEP_URL/openeuler24.03sp3/x86_64" "$FIXTURE/xcat-dep.repo"
unset CORE_URL DEP_URL
REPO_VERSION=devel
run repositories
[ "$status" -eq 0 ]
grep -Fx 'baseurl=https://packages.example.test/xcat/yum/devel/core-snap/openeuler24.03sp3/x86_64' "$FIXTURE/xcat-core.repo"
}
@test "offline native repositories still enforce package signatures" {
mkdir -p "$FIXTURE/offline/core" "$FIXTURE/offline/dep/openeuler24.03sp3/x86_64"
CORE_URL="file://$FIXTURE/offline/core"
DEP_URL="file://$FIXTURE/offline/dep"
run repositories
[ "$status" -eq 0 ]
grep -Fx gpgcheck=1 "$FIXTURE/xcat-core.repo"
grep -Fx gpgcheck=1 "$FIXTURE/xcat-dep.repo"
}
@test "native package operations enforce signatures and propagate failure" {
for command in install_packages_dnf install_packages_yum update_repo_dnf; do
run "$command" -y xCAT
[ "$status" -eq 0 ]
[[ "$output" == *'<--setopt=*.gpgcheck=1>'* ]]
[[ "$output" != *nogpgcheck* && "$output" != *strict=0* ]]
if [[ $command != update_repo_dnf ]]; then
[[ "$output" == *'<install> <initscripts> <xCAT>'* ]]
[[ $command != install_packages_dnf || "$output" == *'<--setopt=strict=1>'* ]]
PACKAGE_STATUS=37
run "$command" -y xCAT
[ "$status" -eq 37 ]
PACKAGE_STATUS=0
fi
done
}
@test "EL and SLES repository selection retains existing behavior" {
while read -r TEST_ID TEST_VERSION_ID expected; do
run repositories
[ "$status" -eq 0 ]
grep -Fx 'baseurl=https://packages.example.test/xcat/yum/latest/xcat-core' "$FIXTURE/xcat-core.repo"
grep -Fx "baseurl=https://packages.example.test/xcat/yum/latest/xcat-dep/$expected/x86_64" "$FIXTURE/xcat-dep.repo"
done <<'CASES'
rocky 9.6 rh9
rhel 10.1 rh10
sles 15.6 sles15
CASES
TEST_ID=rhel TEST_VERSION_ID=10.1
select_platform
run install_packages_dnf -y xCAT
[ "$status" -eq 0 ]
[[ "$output" == *'<--nogpgcheck> <--setopt=strict=0>'* ]]
}
@test "repository replacement selects DNF when yum is absent" {
unset -f yum
type() { [[ $1 != yum ]] && builtin type "$@"; }
grep() { return 1; }
xargs() { cat >/dev/null; }
mv() { return 0; }
run remove_repo_yum xcat-core
[ "$status" -eq 0 ]
[ "$output" = 'dnf <clean> <metadata>' ]
}
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Glob qw(bsd_glob);
use JSON::PP qw(encode_json);
open(my $trace, '>>', $ENV{DNF_TRACE}) or die $!;
print {$trace} encode_json(\@ARGV) . "\n" or die $!;
close($trace) or die $!;
if (!grep({ $_ eq 'clean' } @ARGV) && $ENV{REMOVE_REPO}) {
unlink($_) or die $! for bsd_glob("/etc/yum.repos.d/$ENV{REMOVE_REPO}");
}
exec '/usr/bin/dnf', "--installroot=$ENV{TEST_ROOT}", '--releasever=1',
'--setopt=reposdir=/etc/yum.repos.d', '--setopt=install_weak_deps=False',
'--setopt=tsflags=noscripts', '--noplugins', @ARGV;
die "exec dnf: $!";
@@ -0,0 +1,33 @@
#!/bin/bash
set -eu
root=$TEST_SANDBOX
mkdir -p "$root/etc/yum.repos.d" "$root/tmp" "$root/xcatpost" "$root/dev" \
"$root$TEST_FIXTURES" "$root$TEST_POSTSCRIPTS"
for path in /usr /bin /sbin /lib /lib64; do
[ -d "$path" ] || continue
mkdir -p "$root$path"
/bin/mount --bind "$path" "$root$path"
/bin/mount -o remount,bind,ro "$root$path"
done
for file in passwd group nsswitch.conf ld.so.cache os-release; do
[ ! -f "/etc/$file" ] || cp "/etc/$file" "$root/etc/$file"
done
for path in /etc/dnf /etc/rpm; do
[ -d "$path" ] || continue
mkdir -p "$root$path"
/bin/mount --bind "$path" "$root$path"
/bin/mount -o remount,bind,ro "$root$path"
done
for device in null zero random urandom; do
touch "$root/dev/$device"
/bin/mount --bind "/dev/$device" "$root/dev/$device"
done
/bin/mount --bind "$TEST_FIXTURES" "$root$TEST_FIXTURES"
/bin/mount --bind "$TEST_POSTSCRIPTS" "$root$TEST_POSTSCRIPTS"
/bin/mount -o remount,bind,ro "$root$TEST_POSTSCRIPTS"
/bin/mount --bind "$TEST_REPOS" "$root/etc/yum.repos.d"
if [ -n "${TEST_KEY:-}" ]; then
mkdir -p "$root/etc/pki/rpm-gpg"
cp "$TEST_KEY" "$root/etc/pki/rpm-gpg/RPM-GPG-KEY-openEuler"
fi
exec /usr/sbin/chroot "$root" "$@"
@@ -2,6 +2,7 @@
use strict;
use warnings;
use FindBin;
use File::Copy qw(copy);
use File::Path qw(make_path);
use File::Spec;
use File::Temp qw(tempdir);
@@ -45,26 +46,8 @@ write_text("$dir/bin/dpkg", "#!/bin/sh\nexit 1\n");
write_text("$dir/bin/wget", "#!/bin/sh\nprintf 'wget\\n' >> \"\$RPM_TRACE\"\nexit 1\n");
chmod 0755, glob("$dir/bin/*");
my $wrapper = "$dir/run";
write_text($wrapper, <<'SH');
#!/bin/bash
set -e
/bin/mount --bind "$TEST_REPOS" /etc/yum.repos.d
exec "$@"
SH
copy("$FindBin::Bin/fixtures/postscript-root.sh", $wrapper) or die "copy isolation wrapper: $!";
chmod 0755, $wrapper;
write_text("$dir/bash-env", <<'SH');
echo()
{
case "$*" in
'Warning: the packages '*fallback*|'Warning: the packages '*falling\ back*)
printf 'direct RPM fallback\n' >> "$RPM_TRACE"
builtin echo "$@"
exit 81
;;
esac
builtin echo "$@"
}
SH
for my $caller (qw(ospkgs otherpkgs)) {
for my $os (qw(openeuler20.03sp4 openeuler22.03sp4 openeuler24.03sp1 openeuler24.03sp3 openeuler24.03sp4 openeuler24.03 rhels9.6)) {
@@ -117,11 +100,12 @@ sub run_case {
my ($caller, $os, $list_rc, $install_rc, $repoonly, $empty) = @_;
my $run = tempdir(DIR => $dir, CLEANUP => 1);
make_path("$run/repos");
my $sandbox = tempdir(CLEANUP => 1);
local %ENV = %ENV;
@ENV{qw(TEST_SANDBOX TEST_FIXTURES TEST_POSTSCRIPTS)} = ($sandbox, $dir, $postscripts);
delete @ENV{qw(BASH_ENV ENV ENVLIST OTHERPKGDIR_INTERNET KERNELDIR SDKDIR NODESETSTATE OSPKGDIR VERBOSE)};
@ENV{qw(PATH TEST_REPOS DNF_TRACE RPM_TRACE DNF_LIST_RC DNF_INSTALL_RC)} =
("$dir/bin:$ENV{PATH}", "$run/repos", "$run/dnf", "$run/rpm", $list_rc, $install_rc);
$ENV{BASH_ENV} = "$dir/bash-env";
@ENV{qw(OSVER ARCH MASTER NFSSERVER HTTPPORT INSTALLDIR UPDATENODE)} =
($os, 'x86_64', '192.0.2.1', '192.0.2.1', 8080, '/install', 1);
@ENV{qw(OSPKGS OTHERPKGDIR OTHERPKGS_INDEX OTHERPKGS1)} =
@@ -138,7 +122,7 @@ sub run_case {
}
my $output = do { local $/; <$pipe> } // '';
close($pipe);
my $rc = $? >> 8;
my $rc = (($? & 127) ? 128 + ($? & 127) : $? >> 8);
my $repos = join('', map { read_text($_) } sort glob("$run/repos/*.repo"));
return ($rc, $output, $repos, -f "$run/dnf" ? read_text("$run/dnf") : '',
-f "$run/rpm" ? read_text("$run/rpm") : '');
@@ -2,16 +2,20 @@
use strict;
use warnings;
use FindBin;
use File::Copy qw(copy);
use File::Path qw(make_path);
use File::Spec;
use File::Temp qw(tempdir);
use JSON::PP qw(decode_json encode_json);
use JSON::PP qw(decode_json);
use Test::More;
plan skip_all => 'set XCAT_NATIVE_REPOSITORIES=1 to build and transact signed RPM fixtures'
unless $ENV{XCAT_NATIVE_REPOSITORIES};
plan skip_all => 'requires Linux root and a private mount namespace'
unless $^O eq 'linux' && $> == 0
&& system('unshare', '--mount', '--propagation', 'private', '/bin/true') == 0;
for my $tool (qw(dnf rpm rpmbuild rpmsign createrepo_c gpg python3 runuser)) {
for my $tool (qw(dnf rpm rpmbuild rpmsign createrepo_c gpg gpgconf runuser)) {
plan skip_all => "requires $tool" unless system('sh', '-c', 'command -v "$1" >/dev/null', 'sh', $tool) == 0;
}
plan skip_all => 'requires the packaged openEuler key path'
@@ -22,69 +26,61 @@ unless (-x "$postscripts/otherpkgs") {
require xCAT::TableUtils;
$postscripts = xCAT::TableUtils->getInstallDir() . '/postscripts';
}
my $parent_pid = $$;
my $dir = tempdir(CLEANUP => 1);
my $case_index = 0;
chmod 0755, $dir;
make_path("$dir/bin", "$dir/fixtures", "$dir/gnupg");
chmod 0700, "$dir/gnupg";
write_text("$dir/build-fixtures.py", <<'PY');
from pathlib import Path
import os, shutil, subprocess, sys
b = Path(sys.argv[1]); home = b / 'gnupg'; top = b / 'build'
top.mkdir(); top.chmod(0o777)
subprocess.run(['gpg', '--homedir', str(home), '--batch', '--passphrase', '', '--quick-generate-key',
'xCAT repository test <repo-test@example.invalid>', 'rsa2048', 'sign', '0'], check=True)
key = b / 'key.asc'
key.write_bytes(subprocess.check_output(['gpg', '--homedir', str(home), '--armor', '--export']))
specs = [('oe-scope-os', '1', '', 'old'), ('oe-scope-os', '2', '', 'os'),
('oe-scope-os', '3', '', 'vendor'), ('oe-scope-dependency', '1', '', 'os'),
('oe-scope-extra', '1', 'Requires: oe-scope-dependency = 1', 'other/native')]
for name, version, requires, repo in specs:
spec = b / (name + '-' + version + '.spec')
spec.write_text('''Name: %s
Version: %s
END {
local $?;
capture('gpgconf', '--homedir', "$dir/gnupg", '--kill', 'gpg-agent')
if defined($parent_pid) && $$ == $parent_pid && defined($dir) && -d "$dir/gnupg";
}
my $top = "$dir/build";
make_path($top);
chmod 0777, $top;
checked('gpg', '--homedir', "$dir/gnupg", '--batch', '--passphrase', '',
'--quick-generate-key', 'xCAT repository test <repo-test@example.invalid>', 'rsa2048', 'sign', '0');
checked('gpg', '--homedir', "$dir/gnupg", '--armor', '--output', "$dir/key.asc", '--export');
for my $package (
['oe-scope-os', '1', '', 'old'],
['oe-scope-os', '2', '', 'os'],
['oe-scope-os', '3', '', 'vendor'],
['oe-scope-dependency', '1', '', 'os'],
['oe-scope-extra', '1', 'Requires: oe-scope-dependency = 1', 'other/native'],
) {
my ($name, $version, $requires, $repo) = @$package;
my $spec = "$dir/$name-$version.spec";
write_text($spec, <<"SPEC");
Name: $name
Version: $version
Release: 1
Summary: Isolated package repository fixture
License: MIT
BuildArch: noarch
AutoReqProv: no
%s
%%description
$requires
%description
Isolated package repository fixture.
%%install
mkdir -p %%{buildroot}/usr/share/oe-scope
echo %%{version} > %%{buildroot}/usr/share/oe-scope/%%{name}
%%files
/usr/share/oe-scope/%%{name}
''' % (name, version, requires))
subprocess.run(['runuser', '-u', 'nobody', '--', 'rpmbuild', '--define', '_topdir ' + str(top), '-bb', str(spec)], check=True)
rpm = top / 'RPMS/noarch' / (name + '-' + version + '-1.noarch.rpm')
subprocess.run(['rpmsign', '--define', '_gpg_name repo-test@example.invalid',
'--define', '_gpg_path ' + str(home), '--addsign', str(rpm)], check=True)
dest = b / 'fixtures' / repo; dest.mkdir(parents=True, exist_ok=True)
shutil.copyfile(rpm, dest / rpm.name)
for repo in ('old', 'os', 'vendor', 'other/native'):
dest = b / 'fixtures' / repo
subprocess.run(['createrepo_c', str(dest)], check=True)
shutil.copyfile(key, dest / 'repodata/repomd.xml.key')
PY
my ($fixture_rc, $fixture_output) = capture('python3', "$dir/build-fixtures.py", $dir);
is($fixture_rc, 0, 'signed RPM fixtures are built by an unprivileged user') or BAIL_OUT($fixture_output);
write_text("$dir/bin/dnf", <<'PY');
#!/usr/bin/python3
import json, os, pathlib, sys
import dnf.rpm
args = sys.argv[1:]
with open(os.environ['DNF_TRACE'], 'a') as out:
out.write(json.dumps(args) + '\n')
if 'clean' not in args and os.environ.get('REMOVE_REPO'):
for path in pathlib.Path('/etc/yum.repos.d').glob(os.environ['REMOVE_REPO']):
path.unlink()
os.execv('/usr/bin/dnf', ['dnf', '--installroot=' + os.environ['TEST_ROOT'],
'--releasever=' + dnf.rpm.detect_releasever('/'), '--setopt=reposdir=/etc/yum.repos.d',
'--setopt=install_weak_deps=False', '--setopt=tsflags=noscripts', '--noplugins'] + args)
PY
%install
mkdir -p %{buildroot}/usr/share/oe-scope
echo %{version} > %{buildroot}/usr/share/oe-scope/%{name}
%files
/usr/share/oe-scope/%{name}
SPEC
checked('runuser', '-u', 'nobody', '--', 'rpmbuild', '--define', "_topdir $top", '-bb', $spec);
my $rpm = "$name-$version-1.noarch.rpm";
checked('rpmsign', '--define', '_gpg_name repo-test@example.invalid',
'--define', "_gpg_path $dir/gnupg", '--addsign', "$top/RPMS/noarch/$rpm");
make_path("$dir/fixtures/$repo");
copy("$top/RPMS/noarch/$rpm", "$dir/fixtures/$repo/$rpm") or die "copy $rpm: $!";
}
for my $repo (qw(old os vendor other/native)) {
checked('createrepo_c', "$dir/fixtures/$repo");
copy("$dir/key.asc", "$dir/fixtures/$repo/repodata/repomd.xml.key") or die "copy key: $!";
}
copy("$FindBin::Bin/fixtures/dnf.pl", "$dir/bin/dnf") or die "copy DNF adapter: $!";
write_text("$dir/bin/mount", <<'SH');
#!/bin/sh
printf '%s on %s type nfs\n' "$NFSSERVER" "$INSTALLDIR"
@@ -98,13 +94,7 @@ SH
for my $name (qw(logger dpkg wget)) {
write_text("$dir/bin/$name", "#!/bin/sh\nexit 1\n");
}
write_text("$dir/run", <<'SH');
#!/bin/bash
set -e
/bin/mount --bind "$TEST_REPOS" /etc/yum.repos.d
/bin/mount --bind "$TEST_KEY" /etc/pki/rpm-gpg/RPM-GPG-KEY-openEuler
exec "$@"
SH
copy("$FindBin::Bin/fixtures/postscript-root.sh", "$dir/run") or die "copy isolation wrapper: $!";
chmod 0755, glob("$dir/bin/*"), "$dir/run";
my $os = run_case('ospkgs');
@@ -162,8 +152,7 @@ SKIP: {
unless defined($rpm) && -f $rpm;
my $repo = "$dir/native-key-repo";
make_path($repo);
require File::Copy;
File::Copy::copy($rpm, "$repo/native-key.rpm") or die "copy native key RPM: $!";
copy($rpm, "$repo/native-key.rpm") or die "copy native key RPM: $!";
my ($rc, $out) = capture('createrepo_c', $repo);
is($rc, 0, 'the official native key package fixture has repository metadata') or diag($out);
my $native = run_case('ospkgs', osrepo => $repo, package => 'openEuler-gpg-keys', native_key => 1);
@@ -200,7 +189,9 @@ sub run_case {
($rc, $output) = capture('/usr/bin/rpm', '--root', "$run/root", '-i', "$dir/fixtures/old/oe-scope-os-1-1.noarch.rpm");
die $output if $rc;
}
my $sandbox = tempdir(CLEANUP => 1);
local %ENV = %ENV;
@ENV{qw(TEST_SANDBOX TEST_FIXTURES TEST_POSTSCRIPTS)} = ($sandbox, $dir, $postscripts);
delete @ENV{qw(BASH_ENV ENV ENVLIST OTHERPKGDIR_INTERNET KERNELDIR SDKDIR NODESETSTATE VERBOSE)};
@ENV{qw(PATH TEST_REPOS TEST_ROOT TEST_KEY DNF_TRACE RPM_TRACE REMOVE_REPO)} =
("$dir/bin:$ENV{PATH}", "$run/repos", "$run/root", $key, "$run/dnf", "$run/rpm", $options{remove_repo} // '');
@@ -211,6 +202,7 @@ sub run_case {
my ($rc, $output) = capture('unshare', '--mount', '--propagation', 'private', "$dir/run",
"$postscripts/$caller", ($options{keeprepo} ? '--keeprepo' : ()));
my ($query_rc, $packages) = capture('/usr/bin/rpm', '--root', "$run/root", '-qa', '--qf', '%{NAME} %{VERSION}\n');
die "RPM query failed: $packages" if $query_rc;
my (%packages, $keys);
$keys = 0;
for my $line (split /\n/, $packages) {
@@ -220,13 +212,14 @@ sub run_case {
my $result = { rc => $rc, output => $output, packages => \%packages, keys => $keys,
vendor_preserved => read_text("$run/repos/openEuler.repo") eq $vendor,
dnf => [map { decode_json($_) } split /\n/, read_text("$run/dnf")], rpm => read_text("$run/rpm") };
if (my $evidence = $ENV{XCAT_TEST_EVIDENCE}) {
make_path($evidence);
write_text("$evidence/" . ++$case_index . "-$caller.json", encode_json({ %$result, options => \%options }) . "\n");
}
return $result;
}
sub checked {
my ($rc, $output) = capture(@_);
die "@_ failed ($rc): $output" if $rc;
}
sub capture {
my (@command) = @_;
my $pid = open(my $pipe, '-|');
@@ -238,7 +231,7 @@ sub capture {
}
my $output = do { local $/; <$pipe> } // '';
close($pipe);
return ($? >> 8, $output);
return ((($? & 127) ? 128 + ($? & 127) : $? >> 8), $output);
}
sub read_text {
+142
View File
@@ -0,0 +1,142 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Copy qw(copy);
use File::Path qw(make_path);
use File::Temp qw(tempdir);
use FindBin;
use lib "$FindBin::Bin/../lib", "$FindBin::Bin/../../build-utils/lib";
use Test::More;
use XCAT::Test::File qw(repo_path slurp_repo_file);
use XCAT::BuildUtils qw(openeuler_build_target openeuler_repo_subdir targetarch_from_target);
plan skip_all => 'requires Linux user and mount namespaces'
unless $^O eq 'linux' && system('unshare', '--user', '--map-root-user', '--mount', '/bin/true') == 0;
my $wrapper = "$FindBin::Bin/fixtures/buildrpms-namespace.sh";
my $root = tempdir(CLEANUP => 1);
my $driver = "$root/driver.pl";
write_file($driver, <<'PERL');
BEGIN {
require Parallel::ForkManager;
no warnings 'redefine';
*Parallel::ForkManager::new = sub { die "BUILD_BOUNDARY\n" } if $ENV{BUILD_STOP};
*CORE::GLOBAL::readpipe = sub {
return "$ENV{BUILD_ARCH}\n" if $_[0] eq 'uname -m';
return CORE::readpipe($_[0]);
};
}
do './buildrpms.pl';
die $@ if $@;
PERL
my $native = run_builder('openEuler', '24.03 (LTS-SP3)', '24.03', 'x86_64', '--setup_local_repos');
is($native->{status}, 0, 'full builder creates local repositories for the native default target');
like($native->{core}, qr{/dist/openeuler-24\.03sp3-x86_64/rpms$}m, 'full builder selects the exact native mock target');
like($native->{dep}, qr{/openeuler24\.03sp3/x86_64$}m, 'full builder selects matching dependency artifacts');
like($native->{core}, qr/^gpgcheck=1$/m, 'native core repository requires signatures');
like($native->{dep}, qr/^gpgcheck=1$/m, 'native dependency repository requires signatures');
my $explicit = run_builder('rocky', '9.6', '9.6', 'x86_64', '--setup_local_repos', '--target', 'openeuler-24.03-ppc64le');
is($explicit->{status}, 0, 'an explicit native target overrides the host default for repository setup');
like($explicit->{dep}, qr{/openeuler24\.03/ppc64le$}m, 'explicit native target chooses its own release and architecture');
my $el = run_builder('rocky', '9.6', '9.6', 'x86_64', '--setup_local_repos');
is($el->{status}, 0, 'existing EL local repository setup completes');
like($el->{core}, qr{/dist/rocky\+epel-10-x86_64/rpms$}m, 'existing EL default target is unchanged');
like($el->{dep}, qr{/el9/x86_64$}m, 'existing EL dependency path is unchanged');
for my $missing (qw(core dep)) {
local $ENV{BUILD_MISSING_KEY} = $missing;
my $result = run_builder('openEuler', '24.03 (LTS-SP3)', '24.03', 'x86_64', '--setup_local_repos');
isnt($result->{status}, 0, "setup rejects the missing $missing exported key");
like($result->{stderr}, qr/Missing openEuler repository signing key/, 'setup identifies the absent trust input');
is($result->{core} . $result->{dep}, '', 'setup fails before writing either repository configuration');
}
{
local $ENV{BUILD_STOP} = 1;
my $unsigned = run_builder('openEuler', '24.03 (LTS-SP3)', '24.03', 'x86_64', '--package', 'xCAT-client');
isnt($unsigned->{status}, 0, 'unsigned native binary builds are rejected');
like($unsigned->{stderr}, qr/openEuler binary repository builds require --gpg-sign/, 'the CLI reports the signing requirement');
unlike($unsigned->{stderr}, qr/BUILD_BOUNDARY/, 'unsigned native builds stop before the build scheduler');
for my $case (['native signed', 'openEuler', '--gpg-sign'], ['native source only', 'openEuler', '--source-only'],
['legacy unsigned', 'rocky']) {
my ($label, $id, @args) = @$case;
my $result = run_builder($id, '24.03 (LTS-SP3)', '24.03', 'x86_64', '--package', 'xCAT-client', @args);
like($result->{stderr}, qr/BUILD_BOUNDARY/, "$label reaches the existing build scheduler");
unlike($result->{stderr}, qr/repository builds require --gpg-sign/, "$label preserves its signing policy");
}
}
my $install = run_builder('openEuler', '24.03 (LTS-SP3)', '24.03', 'x86_64', '--install_deps');
is($install->{status}, 0, 'full native prerequisite entry point completes through process boundaries');
unlike($install->{commands}, qr/epel|crb|codeready/i, 'native prerequisites do not enable EL repositories');
unlike($install->{commands}, qr/\bpodman\b/, 'native prerequisites do not request an unrelated container bootstrap');
like($install->{commands}, qr/--setopt=install_weak_deps=False/, 'native prerequisites do not pull optional container dependencies');
like($install->{commands}, qr/--setopt=\*\.gpgcheck=1/, 'native prerequisite installation enforces signatures');
like($install->{commands}, qr/--setopt=strict=1/, 'native prerequisite installation does not skip missing packages');
like($install->{commands}, qr/\bsystemd-nspawn\b/, 'native prerequisites provide the mock isolation executable');
my $el_install = run_builder('rocky', '9.6', '9.6', 'x86_64', '--install_deps');
like($el_install->{commands}, qr/epel-release-latest-10/, 'existing EL prerequisite repository choice is preserved');
my $merge = run_builder('openEuler', '24.03 (LTS-SP3)', '24.03', 'x86_64', '--merge-core-repos');
isnt($merge->{status}, 0, 'native flat repository assembly is rejected');
like($merge->{stderr}, qr/openEuler repositories must retain their release and architecture/, 'native assembly error preserves the per-cell output contract');
my $el_merge = run_builder('rocky', '9.6', '9.6', 'x86_64', '--merge-core-repos', '--output-dir', 'merged', '--input-core-repos', 'native-input');
isnt($el_merge->{status}, 0, 'an EL host cannot flatten copied native inputs');
like($el_merge->{stderr}, qr/openEuler repositories must retain their release and architecture/, 'native build provenance survives copying to an unrelated path');
{
local $ENV{BUILD_DNF_FAILURE} = 37;
my $failed = run_builder('openEuler', '24.03 (LTS-SP3)', '24.03', 'x86_64', '--install_deps');
is($failed->{status}, 37, 'native prerequisite failure terminates the entry point');
}
done_testing();
sub run_builder {
my ($id, $version, $version_id, $arch, @args) = @_;
my $fixture = tempdir(DIR => $root, CLEANUP => 1);
make_path("$fixture/build-utils/lib/XCAT", "$fixture/bin", "$fixture/repos", "$fixture/native-input", "$fixture/locks", "$fixture/home",
"$fixture/dep/openeuler24.03sp3/x86_64", "$fixture/dep/openeuler24.03/ppc64le", "$fixture/dep/el9/x86_64");
copy($ENV{XCAT_TEST_BUILDRPMS} || repo_path('buildrpms.pl'), "$fixture/buildrpms.pl") or die $!;
copy(repo_path('build-utils/lib/XCAT/BuildUtils.pm'), "$fixture/build-utils/lib/XCAT/BuildUtils.pm") or die $!;
copy($driver, "$fixture/driver.pl") or die $!;
write_file("$fixture/Version", "2.18.0\n");
write_file("$fixture/Gitepoch", "1756000000\n");
write_file("$fixture/native-input/buildinfo.txt", "BUILD_TARGET=openeuler-24.03sp3-x86_64\n");
write_file("$fixture/os-release", "ID=$id\nVERSION=\"$version\"\nVERSION_ID=$version_id\n");
for my $target ('openeuler-24.03sp3-x86_64', 'openeuler-24.03-ppc64le') {
my $subdir = openeuler_repo_subdir($target);
for my $key (['core', "$fixture/dist/$target/rpms"], ['dep', "$fixture/dep/$subdir"]) {
make_path("$key->[1]/repodata");
write_file("$key->[1]/repodata/repomd.xml.key", 'existing exported key')
unless ($ENV{BUILD_MISSING_KEY} || '') eq $key->[0];
}
}
write_file("$fixture/bin/dnf", "#!/bin/sh\nprintf '%s\\n' \"\$*\" >> \"\$BUILD_FIXTURE/commands\"\nexit \"\${BUILD_DNF_FAILURE:-0}\"\n");
write_file("$fixture/bin/systemctl", "#!/bin/sh\nexit 0\n");
write_file("$fixture/bin/rpmdev-setuptree", "#!/bin/sh\nexit 0\n");
chmod 0755, map {"$fixture/bin/$_"} qw(dnf systemctl rpmdev-setuptree);
local %ENV = (%ENV, HOME => "$fixture/home", BUILD_FIXTURE => $fixture, BUILD_ARCH => $arch, PATH => "$fixture/bin:$ENV{PATH}");
my $pid = fork();
die $! unless defined $pid;
if (!$pid) {
chdir($fixture) or die $!;
open(STDOUT, '>', 'stdout') or die $!;
open(STDERR, '>', 'stderr') or die $!;
exec('unshare', '--user', '--map-root-user', '--mount', 'bash', $wrapper, $^X, 'driver.pl', @args, '--xcat_dep_path', "$fixture/dep") or die $!;
}
waitpid($pid, 0);
my $status = (($? & 127) ? 128 + ($? & 127) : $? >> 8);
my $result = {status => $status, core => read_file("$fixture/repos/xcat-core-local.repo"),
dep => read_file("$fixture/repos/xcat-dep.repo"), commands => read_file("$fixture/commands"), stderr => read_file("$fixture/stderr")};
diag(read_file("$fixture/stderr")) if $status && !$ENV{BUILD_DNF_FAILURE} && !$ENV{BUILD_MISSING_KEY}
&& !$ENV{BUILD_STOP} && !grep {$_ eq '--merge-core-repos'} @args;
return $result;
}
sub write_file {
my ($path, $contents) = @_;
open(my $fh, '>', $path) or die "$path: $!";
print {$fh} $contents;
close($fh) or die $!;
}
sub read_file {
my ($path) = @_;
return '' unless -f $path;
open(my $fh, '<', $path) or die $!;
return do {local $/; scalar <$fh>} || '';
}
@@ -13,6 +13,10 @@ plan skip_all => 'requires unprivileged Linux RPM tooling'
unless $^O eq 'linux' && $>
&& system('sh', '-c', 'command -v rpmbuild >/dev/null && command -v rpm2cpio >/dev/null && command -v cpio >/dev/null') == 0;
plan skip_all => 'requires user and mount namespaces'
if system('unshare', '--user', '--map-root-user', '--mount', '/bin/true');
my $wrapper = "$FindBin::Bin/fixtures/buildrpms-namespace.sh";
my $root = tempdir(CLEANUP => !$ENV{XCAT_RELEASE_TEST_KEEP});
diag("Release contract fixtures: $root") if $ENV{XCAT_RELEASE_TEST_KEEP};
my $builder = $ENV{XCAT_RELEASE_TEST_BUILDER} || repo_path('buildrpms.pl');
@@ -83,6 +87,8 @@ for my $case (@cases) {
for my $failure (qw(missing-core duplicate-dep copy tar)) {
my $result = run_builder('openeuler-24.03sp3-x86_64', 'openeuler24.03sp3/x86_64', undef, $failure);
isnt($result->{status}, 0, "$failure stops the native builder");
like($result->{output}, qr/Error staging xCAT-release sources/,
'the copy fault reaches production source staging') if $failure eq 'copy';
my $fixture = $result->{fixture};
is(read_file("$fixture/home/rpmbuild/SOURCES/xCAT-release-2.19.0.tar.gz"), 'prior archive',
"$failure preserves the preceding source archive");
@@ -110,12 +116,7 @@ sub run_builder {
my ($target, $subdir, $default, $failure) = @_;
my $fixture = tempdir(DIR => $root, CLEANUP => !$ENV{XCAT_RELEASE_TEST_KEEP});
make_path(map {"$fixture/$_"} qw(build-utils/lib/XCAT bin mock locks tmp xCAT-release home/rpmbuild/SOURCES));
my $body = read_file($builder);
$body =~ s{'/etc/os-release'}{'$fixture/os-release'}g;
$body =~ s{/etc/mock/}{$fixture/mock/}g;
$body =~ s{/var/lock/}{$fixture/locks/}g;
$body =~ s/\$ENV\{HOME\}/\$ENV{XCAT_RELEASE_TEST_HOME}/g;
write_file("$fixture/buildrpms.pl", $body);
copy($builder, "$fixture/buildrpms.pl") or die $!;
copy(repo_path('build-utils/lib/XCAT/BuildUtils.pm'), "$fixture/build-utils/lib/XCAT/BuildUtils.pm") or die $!;
copy($spec, "$fixture/xCAT-release/xCAT-release.spec") or die $!;
write_file("$fixture/xCAT-release/$_", $original{$_}) for @files;
@@ -153,11 +154,11 @@ SH
"baseurl=https://xcat.org/files/xcat/repos/yum/latest/xcat-dep/rh\$releasever/\$basearch\n")
if $failure eq 'duplicate-dep';
}
local %ENV = (%ENV, PATH => "$fixture/bin:$ENV{PATH}", TMPDIR => "$fixture/tmp", XCAT_RELEASE_TEST_FIXTURE => $fixture,
XCAT_RELEASE_TEST_HOME => "$fixture/home", XCAT_RELEASE_TEST_NATIVE => defined($subdir) ? 1 : 0,
local %ENV = (%ENV, PATH => "$fixture/bin:$ENV{PATH}", TMPDIR => "$fixture/tmp", XCAT_RELEASE_TEST_FIXTURE => $fixture, BUILD_FIXTURE => $fixture,
HOME => "$fixture/home", LC_ALL => 'C',
XCAT_RELEASE_TEST_FAILURE => ($failure && $failure eq 'copy' ? 'cp' : $failure || ''));
my @target = $default ? () : ('--target', $target);
my ($status, $output) = run($fixture, $^X, 'buildrpms.pl', '--package', 'xCAT-release', @target,
my ($status, $output) = run($fixture, 'unshare', '--user', '--map-root-user', '--mount', 'bash', $wrapper, $^X, 'buildrpms.pl', '--package', 'xCAT-release', @target,
(defined($subdir) ? '--gpg-sign' : ()),
'--release', 'releasecontract', '--mock-uniqueext', 'release-contract', '--nproc', '1');
write_file("$fixture/builder-output", $output);
@@ -182,11 +183,13 @@ while (@ARGV) {
elsif ($arg =~ /^(?:-r|--spec|--sources|--resultdir|--rebuild)$/) {$args{$arg} = shift;}
else {$args{$arg} = 1;}
}
die "Missing selected mock configuration\n" unless -f "/etc/mock/$args{'-r'}.cfg";
exit 0 if $args{'--init'};
my $top = "$fixture/mock-rpm";
make_path(map {"$top/$_"} qw(BUILD BUILDROOT RPMS SOURCES SPECS SRPMS));
my @command = ('rpmbuild', @defines, '--define', "_topdir $top", '--undefine', 'openEuler');
push @command, '--define', 'openEuler 2' if $ENV{XCAT_RELEASE_TEST_NATIVE};
my @command = ('rpmbuild', @defines, '--define', "_topdir $top");
push @command, ($args{'-r'} // '') =~ m{(?:^|[-/])openeuler-}
? ('--define', 'openEuler 2') : ('--undefine', 'openEuler');
my @built;
if ($args{'--buildsrpm'}) {
push @command, '--define', "_sourcedir $args{'--sources'}", '-bs', $args{'--spec'};
@@ -195,7 +198,7 @@ if ($args{'--buildsrpm'}) {
push @command, '--rebuild', $args{'--rebuild'};
@built = ("$top/RPMS/noarch/*.rpm");
} else {die "Unexpected mock operation\n";}
system(@command) == 0 or exit(($? >> 8) || 1);
system(@command) == 0 or exit(($? & 127) ? 128 + ($? & 127) : ($? >> 8) || 1);
make_path($args{'--resultdir'});
for my $file (map {glob($_)} @built) {copy($file, $args{'--resultdir'}) or die $!;}
PERL
@@ -213,7 +216,8 @@ sub run {
exec(@command) or die "exec @command: $!";
}
waitpid($pid, 0);
return ($? ? (($? >> 8) || 1) : 0, read_file($log));
my $status = ($? & 127) ? 128 + ($? & 127) : $? >> 8;
return ($status, read_file($log));
}
sub write_file {
my ($path, $contents) = @_;
@@ -0,0 +1,18 @@
#!/bin/bash
set -eu
mount --make-rslave /
etc="$BUILD_FIXTURE/etc"
mkdir -p "$etc/mock" "$etc/yum.repos.d"
for file in passwd group nsswitch.conf ld.so.cache; do
[ ! -f "/etc/$file" ] || /bin/cp "/etc/$file" "$etc/$file"
done
/bin/cp "$BUILD_FIXTURE/os-release" "$etc/os-release"
mount --bind "$etc" /etc
mount --bind "$BUILD_FIXTURE/locks" /var/lock
if [ -d "$BUILD_FIXTURE/repos" ]; then
mount --bind "$BUILD_FIXTURE/repos" /etc/yum.repos.d
fi
if [ -d "$BUILD_FIXTURE/mock" ]; then
mount --bind "$BUILD_FIXTURE/mock" /etc/mock
fi
exec "$@"
@@ -182,11 +182,11 @@ sub run {
die $! unless defined($pid);
if (!$pid) {
open(STDOUT, '>', $log) or die $!;
open(STDERR, '>', "$log.stderr") or die $!;
open(STDERR, '>&', STDOUT) or die $!;
exec(@command) or die $!;
}
waitpid($pid, 0);
my $status = $? >> 8;
my $status = (($? & 127) ? 128 + ($? & 127) : $? >> 8);
open(my $fh, '<', $log) or die $!;
my $output = do { local $/; <$fh> } // '';
close($fh);
@@ -14,6 +14,7 @@ plan skip_all => 'requires native openEuler, an unprivileged user, rpmbuild and
unless $^O eq 'linux' && $> && -f '/etc/openEuler-release'
&& -d '/usr/lib/dracut' && system('sh', '-c', 'command -v rpmbuild >/dev/null && unshare -Ur -m true') == 0;
local $ENV{PATH} = "$ENV{PATH}:/usr/sbin:/sbin";
my $root = abs_path("$RealBin/../..");
my $spec = $ENV{XCAT_GENESIS_RELEASE_SPEC} // "$root/xCAT-genesis-builder/xCAT-genesis-base.spec";
my $tmp = tempdir(CLEANUP => 1);
@@ -127,7 +127,7 @@ for my $case (@cases) {
exec('unshare', '-mn', '--', "$tmp/namespace") or die $!;
}
waitpid($pid, 0);
my $status = $? >> 8;
my $status = (($? & 127) ? 128 + ($? & 127) : $? >> 8);
is($status, 0, "pass $pass invokes the full production module in isolation");
if ($status) { diag(read_file("$fixture/stderr-$pass.log")); next; }
my $result = decode_json(read_file("$fixture/result-$pass.json"));
@@ -17,6 +17,7 @@ write_file($driver, <<'PERL');
use strict;
use warnings;
use Storable qw(nstore);
use Text::ParseWords qw(shellwords);
our (@commands, $reached, $config, $restored);
BEGIN {
*CORE::GLOBAL::readpipe = sub {
@@ -28,7 +29,10 @@ BEGIN {
my $command = join(' ', @_);
push @commands, $command;
if ($command =~ /^\s*(?:dnf|yum) /) {
open(my $file, '<', "/tmp/genimage.$$.yum.conf") or die $!;
my @args = shellwords($command);
my ($index) = grep { $args[$_] eq '-c' } 0 .. $#args - 1;
die 'Package command omitted its configuration' unless defined($index);
open(my $file, '<', $args[$index + 1]) or die $!;
$config = do { local $/; <$file> };
close($file);
my $phase = $command =~ / erase .*pre-remove/ ? 'pre-remove'
@@ -126,7 +130,7 @@ sub run_case {
die "exec: $!";
}
waitpid($pid, 0);
my $status = $? >> 8;
my $status = (($? & 127) ? 128 + ($? & 127) : $? >> 8);
open(my $file, '<', "$case/output") or die $!;
my $output = do { local $/; <$file> };
close($file);
@@ -6,7 +6,7 @@ use File::Copy qw(copy);
use File::Path qw(make_path);
use File::Temp qw(tempdir);
use FindBin;
use lib "$FindBin::Bin/../lib";
use lib "$FindBin::Bin/../lib", "$FindBin::Bin/../../perl-xCAT", "$FindBin::Bin/../../xCAT-server/lib/perl";
use Test::More;
use XCAT::Test::File qw(repo_path slurp_repo_file);
@@ -17,19 +17,21 @@ plan skip_all => 'mount and network namespaces unavailable'
my $tmp = tempdir(DIR => '/var/tmp', CLEANUP => !$ENV{XCAT_POST_REPOS_KEEP});
diag("fixtures: $tmp") if $ENV{XCAT_POST_REPOS_KEEP};
my $scriptdir = 'xCAT-server/share/xcat/install/scripts';
my $postscript = slurp_repo_file("$scriptdir/post.rhels8");
my $library = slurp_repo_file("$scriptdir/scriptlib");
my $include = '#INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/scriptlib#';
$postscript =~ s/^\Q$include\E$/$library/m
or BAIL_OUT('Unable to render the scriptlib include');
my $preamble = <<'SH';
compgen() { return 1; }
nmcli() { return 0; }
SH
$postscript =~ s/\A(#![^\n]*\n)/$1$preamble/
or BAIL_OUT('Unable to stage the network fixture');
write_file("$tmp/post.rhels8", $postscript);
make_path("$tmp/db", "$tmp/bin");
$ENV{XCATROOT} = repo_path('xCAT-server');
$ENV{XCATCFG} = "SQLite:$tmp/db";
$ENV{MASTER_IP} = '192.0.2.1';
require xCAT::Table;
require xCAT::Template;
my $site = xCAT::Table->new('site', -create => 1);
$site->setAttribs({key => 'xcatdebugmode'}, {value => '0'});
$site->close();
my $error = xCAT::Template->subvars(repo_path('xCAT-server/share/xcat/install/scripts/post.rhels8'),
"$tmp/post.rhels8", 'node', undef, '/install/media', 'openeuler', undef,
{xcatmaster => '192.0.2.1'});
die "Cannot render post.rhels8: $error" if $error;
write_file("$tmp/bin/nmcli", "#!/bin/sh\nexit 0\n");
chmod 0755, "$tmp/bin/nmcli" or die $!;
is(system('/bin/bash', '-n', "$tmp/post.rhels8"), 0,
'the complete rendered post owner has valid Bash syntax');
write_file("$tmp/namespace", <<'SH');
@@ -42,13 +44,7 @@ mount --bind "$XCAT_POST_REPOS_FIXTURE/etc" /etc
exec /bin/bash "$XCAT_POST_REPOS_OWNER"
SH
chmod 0755, "$tmp/namespace" or die $!;
my @legacy = qw(
oracle-linux-ol8.repo oracle-linux-ol9.repo uek-ol8.repo uek-ol9.repo
Rocky-AppStream.repo Rocky-BaseOS.repo Rocky-Extras.repo rocky.repo
rocky-extras.repo CentOS-Base.repo centos.repo centos-addons.repo
almalinux-ha.repo almalinux-nfv.repo almalinux-powertools.repo
almalinux.repo almalinux-resilientstorage.repo almalinux-rt.repo
);
my @legacy = qw(Rocky-BaseOS.repo almalinux.repo CentOS-Base.repo oracle-linux-ol8.repo);
my $enabled = "[fixture]\nenabled=1\ngpgcheck=1\nbaseurl=https://repo.invalid/\n";
my $disabled = "[fixture]\nenabled=0\ngpgcheck=1\nbaseurl=https://repo.invalid/\n";
my $native = "[OS]\nenabled=1\ngpgcheck=1\n[update]\n \tenabled = 1\n[disabled]\nenabled=0\n#enabled=1\n";
@@ -77,7 +73,7 @@ for my $case (
local %ENV = (%ENV, XCATDEBUGMODE => '0', MASTER_IP => '192.0.2.1',
XCAT_POST_REPOS_FIXTURE => $fixture, XCAT_POST_REPOS_OWNER => "$tmp/post.rhels8",
XCAT_POST_HOST_MOUNT => $host_mount, XCAT_POST_HOST_NET => $host_net,
PATH => '/usr/sbin:/usr/bin:/sbin:/bin', LC_ALL => 'C');
PATH => "$tmp/bin:/usr/sbin:/usr/bin:/sbin:/bin", LC_ALL => 'C');
for my $pass (1, 2) {
my $pid = fork(); die $! unless defined $pid;
if (!$pid) {
@@ -149,7 +149,7 @@ sub run_case {
die $!;
}
waitpid($pid, 0);
return ($? >> 8, read_file("$fixture/commands"), read_file("$fixture/output"), $fixture);
return ((($? & 127) ? 128 + ($? & 127) : $? >> 8), read_file("$fixture/commands"), read_file("$fixture/output"), $fixture);
}
for my $case (
@@ -26,7 +26,7 @@ foreach my $role (@roles) {
my ($rc, $requires) = run('rpmspec', '-q', '--requires', '--target', 'x86_64', @macros,
'--define', 'openEuler 2', $spec);
is($rc, 0, "$name native dependencies parse through RPM");
BAIL_OUT("Unable to parse $spec: $requires") if $rc;
die("Unable to parse $spec: $requires") if $rc;
my @dhcp = grep { /\bkea(?:-hooks)?\b|\/usr\/sbin\/dhcpd/ } split(/\n/, $requires);
ok(@dhcp, "$name emits DHCP policy requirements");
$consumer{$name} = build_fixture("policy-$name", join('', map {"Requires: $_\n"} @dhcp));
@@ -84,9 +84,9 @@ foreach my $scenario (@scenarios) {
my ($label, $success, @providers) = @$scenario;
my $db = tempdir(DIR => $root, CLEANUP => 1);
my ($init_rc, $init_output) = run('rpm', '--dbpath', $db, '--initdb');
BAIL_OUT("Cannot initialize test RPM database: $init_output") if $init_rc;
die("Cannot initialize test RPM database: $init_output") if $init_rc;
my ($install_rc, $install_output) = run('rpm', '--dbpath', $db, '--justdb', '--nodeps', '-i', map {$provider{$_}} @providers);
BAIL_OUT("Cannot seed test RPM database: $install_output") if $install_rc;
die("Cannot seed test RPM database: $install_output") if $install_rc;
foreach my $role (@roles) {
my ($rc, $output) = run('rpm', '--dbpath', $db, '--test', '-i', $consumer{$role->[0]});
is($rc == 0 ? 1 : 0, $success, "$role->[0] $label has the expected dependency result")
@@ -108,9 +108,9 @@ sub build_fixture {
print {$fh} "\n%files\n", $file || '', "\n";
close($fh) or die "$spec: $!";
my ($rc, $output) = run('rpmbuild', '-bb', '--define', "_topdir $root", '--define', '_build_id_links none', $spec);
BAIL_OUT("Cannot build RPM fixture $name: $output") if $rc;
die("Cannot build RPM fixture $name: $output") if $rc;
my $rpm = "$root/RPMS/noarch/$name-1-1.noarch.rpm";
BAIL_OUT("RPM fixture missing: $rpm") unless -f $rpm;
die("RPM fixture missing: $rpm") unless -f $rpm;
return $rpm;
}
sub run {
@@ -124,7 +124,7 @@ sub run {
exec(@command) or die "@command: $!";
}
waitpid($pid, 0);
my $status = $? >> 8;
my $status = (($? & 127) ? 128 + ($? & 127) : $? >> 8);
open(my $fh, '<', $log) or die "$log: $!";
return ($status, do {local $/; scalar <$fh>} || '');
}
@@ -13,9 +13,9 @@ use DBI;
my $bindir = $ENV{XCAT_TEST_PG_BINDIR};
plan skip_all => 'Set XCAT_TEST_PG_BINDIR to native PostgreSQL binaries' unless $bindir;
BAIL_OUT('The private PostgreSQL fixture must run without root privileges') if $< == 0;
die('The private PostgreSQL fixture must run without root privileges') if $< == 0;
for my $command (qw(initdb pg_ctl postgres psql createdb)) {
BAIL_OUT("Missing native PostgreSQL program $command") unless -x "$bindir/$command";
die("Missing native PostgreSQL program $command") unless -x "$bindir/$command";
}
BEGIN {
*CORE::GLOBAL::exit = sub { die bless { status => $_[0] || 0 }, 'PgSchemaTestExit' };
@@ -47,7 +47,7 @@ push @init, ('-L', $ENV{XCAT_TEST_PG_SHAREDIR}) if $ENV{XCAT_TEST_PG_SHAREDIR};
is(quiet_system(@init), 0, 'native initdb creates a private cluster');
my $start = quiet_system("$bindir/pg_ctl", '-D', "$tmp/data", '-l', "$tmp/server.log", '-o', "-k $tmp/socket -h '' -p $ENV{PGPORT}", '-w', 'start');
is($start, 0, 'private PostgreSQL starts with only a private Unix socket');
BAIL_OUT('Unable to start the private PostgreSQL fixture') if $start != 0;
die('Unable to start the private PostgreSQL fixture') if $start != 0;
$started = 1;
my $admin = DBI->connect("dbi:Pg:dbname=postgres;host=$ENV{PGHOST};port=$ENV{PGPORT}", 'postgres', '', { RaiseError => 1, PrintError => 0, AutoCommit => 1 });
my ($version) = $admin->selectrow_array('SHOW server_version_num');
@@ -64,7 +64,7 @@ my ($help, $load_error);
$load_error = $@;
}
is(ref($load_error), 'PgSchemaTestExit', 'complete pgsqlsetup loads through its help path');
BAIL_OUT("Unable to load pgsqlsetup: $load_error") unless ref($load_error) eq 'PgSchemaTestExit' && $load_error->{status} == 0;
die("Unable to load pgsqlsetup: $load_error") unless ref($load_error) eq 'PgSchemaTestExit' && $load_error->{status} == 0;
{
local $::osname = 'Linux';
local $::linuxos = 'openeuler24.03sp3';
@@ -40,7 +40,7 @@ sub public_identity {
my @types = qw(dsa rsa ecdsa ed25519);
for my $type (@types) {
my $rc = system('/usr/bin/ssh-keygen', '-q', '-t', $type, '-f', "$tmp/keys/$type", '-N', '', '-C', '');
BAIL_OUT("native ssh-keygen cannot generate disposable $type fixture") if $rc;
die("native ssh-keygen cannot generate disposable $type fixture") if $rc;
}
for my $name (qw(remoteshell xcatlib.sh remoteshell-sshd-config)) {
copy("$source/xCAT/postscripts/$name", "$tmp/bin/$name") or die $!;
@@ -119,7 +119,7 @@ for my $case (
'unshare', '-m', '--', "$tmp/bin/namespace", "$tmp/bin/remoteshell") or die $!;
$output = do { local $/; <$pipe> };
close($pipe);
$rc = $? >> 8;
$rc = (($? & 127) ? 128 + ($? & 127) : $? >> 8);
}
if ($fail_command) {
is($rc, 1, "$label run $run reports failed key protection");
@@ -140,6 +140,7 @@ for my $case (@cases) {
is(system('unshare', '-mn', '--', "$tmp/bin/namespace"), 0, "$name: isolated driver completed");
my $rc = read_file("$fixture/postscript-1.rc");
chomp $rc;
die "Missing postscript status for $name" unless $rc =~ /^\d+$/;
if ($failure) {
isnt($rc, 0, "$name: failure reaches postscript exit");
is(read_file("$fixture/logger.log"), '', "$name: no success log");
@@ -103,8 +103,8 @@ for my $case (['mn', 0], ['snlocal', 0], ['snforward', 0], ['cn', 0], ['mn', 1],
is(read_file("$evidence/helper.rc"), "1\n", "$name: shared restart helper retains its existing contract");
is(read_file("$evidence/services-postscript.log"), '', "$name: postscript does not attempt service activation");
if ($invalid) {
isnt(read_file("$evidence/postscript-1.rc"), "0\n", "$name: parser failure reaches the postscript exit");
isnt(read_file("$evidence/parser.rc"), "0\n", "$name: actual native parser rejects the configuration");
is(read_file("$evidence/postscript-1.rc"), "1\n", "$name: parser failure reaches the postscript exit");
is(read_file("$evidence/parser.rc"), "1\n", "$name: actual native parser rejects the configuration");
is(read_file("$evidence/logger.log"), '', "$name: no success log follows invalid configuration");
} else {
is(read_file("$evidence/postscript-1.rc"), "0\n", "$name: configuration completes successfully");
+3 -3
View File
@@ -59,9 +59,9 @@ for my $case (
"$fixture retains the exact release and architecture");
}
for my $version ('20.03-LTS-SP4', '22.03-LTS-SP4', '24.03-LTS-SP1', '24.03-LTS-SP3') {
(my $os = lc($version)) =~ s/-lts//;
$os =~ s/-sp/sp/;
for my $case (['20.03-LTS-SP4', '20.03sp4'], ['22.03-LTS-SP4', '22.03sp4'],
['24.03-LTS-SP1', '24.03sp1'], ['24.03-LTS-SP3', '24.03sp3']) {
my ($version, $os) = @$case;
my $responses = inspect_media(media_tree($version, 'x86_64'));
like($responses->[0]{info} || '', qr/^DISTNAME:openeuler\Q$os\E\n/,
"$version keeps its service pack");
-149
View File
@@ -1,13 +1,9 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Copy qw(copy);
use File::Path qw(make_path);
use File::Temp qw(tempdir);
use FindBin;
use lib "$FindBin::Bin/../lib", "$FindBin::Bin/../../build-utils/lib";
use Test::More;
use XCAT::Test::File qw(repo_path slurp_repo_file);
use XCAT::BuildUtils qw(openeuler_build_target openeuler_repo_subdir targetarch_from_target);
foreach my $version (['20.03 (LTS-SP4)', '20.03sp4'], ['22.03 (LTS-SP4)', '22.03sp4'],
@@ -30,149 +26,4 @@ foreach my $invalid ('openeuler-24.09-x86_64', 'openeuler-25.03-x86_64', 'openeu
like($@, qr/Unsupported openEuler build target/, "$invalid is rejected");
}
my $root = tempdir(CLEANUP => 1);
my $driver = "$root/driver.pl";
write_file($driver, <<'PERL');
BEGIN {
require File::Slurper;
require Parallel::ForkManager;
no warnings 'redefine';
*Parallel::ForkManager::new = sub { die "BUILD_BOUNDARY\n" } if $ENV{BUILD_STOP};
my $write = \&File::Slurper::write_text;
*File::Slurper::write_text = sub {
my ($path, @rest) = @_;
$path =~ s{^/etc/yum\.repos\.d/}{$ENV{BUILD_FIXTURE}/repos/};
return $write->($path, @rest);
};
*CORE::GLOBAL::readpipe = sub {
return "$ENV{BUILD_ARCH}\n" if $_[0] eq 'uname -m';
return CORE::readpipe($_[0]);
};
*CORE::GLOBAL::system = sub {
if (@_ == 1 && $_[0] =~ /source \/etc\/os-release/) {
my $shell = "$ENV{BUILD_FIXTURE}/install.sh";
CORE::open(my $fh, '>', $shell) or die $!;
print {$fh} 'source() { . "$BUILD_FIXTURE/os-release"; }', "\n", $_[0];
close($fh);
return CORE::system('bash', $shell);
}
return 0;
};
}
do './buildrpms.pl';
die $@ if $@;
PERL
my $native = run_builder('openEuler', '24.03 (LTS-SP3)', '24.03', 'x86_64', '--setup_local_repos');
is($native->{status}, 0, 'full builder creates local repositories for the native default target');
like($native->{core}, qr{/dist/openeuler-24\.03sp3-x86_64/rpms$}m, 'full builder selects the exact native mock target');
like($native->{dep}, qr{/openeuler24\.03sp3/x86_64$}m, 'full builder selects matching dependency artifacts');
like($native->{core}, qr/^gpgcheck=1$/m, 'native core repository requires signatures');
like($native->{dep}, qr/^gpgcheck=1$/m, 'native dependency repository requires signatures');
my $explicit = run_builder('rocky', '9.6', '9.6', 'x86_64', '--setup_local_repos', '--target', 'openeuler-24.03-ppc64le');
is($explicit->{status}, 0, 'an explicit native target overrides the host default for repository setup');
like($explicit->{dep}, qr{/openeuler24\.03/ppc64le$}m, 'explicit native target chooses its own release and architecture');
my $el = run_builder('rocky', '9.6', '9.6', 'x86_64', '--setup_local_repos');
is($el->{status}, 0, 'existing EL local repository setup completes');
like($el->{core}, qr{/dist/rocky\+epel-10-x86_64/rpms$}m, 'existing EL default target is unchanged');
like($el->{dep}, qr{/el9/x86_64$}m, 'existing EL dependency path is unchanged');
for my $missing (qw(core dep)) {
local $ENV{BUILD_MISSING_KEY} = $missing;
my $result = run_builder('openEuler', '24.03 (LTS-SP3)', '24.03', 'x86_64', '--setup_local_repos');
isnt($result->{status}, 0, "setup rejects the missing $missing exported key");
like($result->{stderr}, qr/Missing openEuler repository signing key/, 'setup identifies the absent trust input');
is($result->{core} . $result->{dep}, '', 'setup fails before writing either repository configuration');
}
{
local $ENV{BUILD_STOP} = 1;
my $unsigned = run_builder('openEuler', '24.03 (LTS-SP3)', '24.03', 'x86_64', '--package', 'xCAT-client');
isnt($unsigned->{status}, 0, 'unsigned native binary builds are rejected');
like($unsigned->{stderr}, qr/openEuler binary repository builds require --gpg-sign/, 'the CLI reports the signing requirement');
unlike($unsigned->{stderr}, qr/BUILD_BOUNDARY/, 'unsigned native builds stop before the build scheduler');
for my $case (['native signed', 'openEuler', '--gpg-sign'], ['native source only', 'openEuler', '--source-only'],
['legacy unsigned', 'rocky']) {
my ($label, $id, @args) = @$case;
my $result = run_builder($id, '24.03 (LTS-SP3)', '24.03', 'x86_64', '--package', 'xCAT-client', @args);
like($result->{stderr}, qr/BUILD_BOUNDARY/, "$label reaches the existing build scheduler");
unlike($result->{stderr}, qr/repository builds require --gpg-sign/, "$label preserves its signing policy");
}
}
my $install = run_builder('openEuler', '24.03 (LTS-SP3)', '24.03', 'x86_64', '--install_deps');
is($install->{status}, 0, 'full native prerequisite entry point completes through process boundaries');
unlike($install->{commands}, qr/epel|crb|codeready/i, 'native prerequisites do not enable EL repositories');
unlike($install->{commands}, qr/\bpodman\b/, 'native prerequisites do not request an unrelated container bootstrap');
like($install->{commands}, qr/--setopt=install_weak_deps=False/, 'native prerequisites do not pull optional container dependencies');
like($install->{commands}, qr/--setopt=\*\.gpgcheck=1/, 'native prerequisite installation enforces signatures');
like($install->{commands}, qr/--setopt=strict=1/, 'native prerequisite installation does not skip missing packages');
like($install->{commands}, qr/\bsystemd-nspawn\b/, 'native prerequisites provide the mock isolation executable');
my $el_install = run_builder('rocky', '9.6', '9.6', 'x86_64', '--install_deps');
like($el_install->{commands}, qr/epel-release-latest-10/, 'existing EL prerequisite repository choice is preserved');
my $merge = run_builder('openEuler', '24.03 (LTS-SP3)', '24.03', 'x86_64', '--merge-core-repos');
isnt($merge->{status}, 0, 'native flat repository assembly is rejected');
like($merge->{stderr}, qr/openEuler repositories must retain their release and architecture/, 'native assembly error preserves the per-cell output contract');
my $el_merge = run_builder('rocky', '9.6', '9.6', 'x86_64', '--merge-core-repos', '--output-dir', 'merged', '--input-core-repos', 'native-input');
isnt($el_merge->{status}, 0, 'an EL host cannot flatten copied native inputs');
like($el_merge->{stderr}, qr/openEuler repositories must retain their release and architecture/, 'native build provenance survives copying to an unrelated path');
{
local $ENV{BUILD_DNF_FAILURE} = 37;
my $failed = run_builder('openEuler', '24.03 (LTS-SP3)', '24.03', 'x86_64', '--install_deps');
is($failed->{status}, 37, 'native prerequisite failure terminates the entry point');
}
done_testing();
sub run_builder {
my ($id, $version, $version_id, $arch, @args) = @_;
my $fixture = tempdir(DIR => $root, CLEANUP => 1);
make_path("$fixture/build-utils/lib/XCAT", "$fixture/bin", "$fixture/repos", "$fixture/native-input", "$fixture/locks", "$fixture/home",
"$fixture/dep/openeuler24.03sp3/x86_64", "$fixture/dep/openeuler24.03/ppc64le", "$fixture/dep/el9/x86_64");
my $body = read_file($ENV{XCAT_TEST_BUILDRPMS} || repo_path('buildrpms.pl'));
$body =~ s{'/etc/os-release'}{'$fixture/os-release'}g;
$body =~ s{/var/lock/}{$fixture/locks/}g;
write_file("$fixture/buildrpms.pl", $body);
copy(repo_path('build-utils/lib/XCAT/BuildUtils.pm'), "$fixture/build-utils/lib/XCAT/BuildUtils.pm") or die $!;
copy($driver, "$fixture/driver.pl") or die $!;
write_file("$fixture/Version", "2.18.0\n");
write_file("$fixture/Gitepoch", "1756000000\n");
write_file("$fixture/native-input/buildinfo.txt", "BUILD_TARGET=openeuler-24.03sp3-x86_64\n");
write_file("$fixture/os-release", "ID=$id\nVERSION=\"$version\"\nVERSION_ID=$version_id\n");
for my $target ('openeuler-24.03sp3-x86_64', 'openeuler-24.03-ppc64le') {
my $subdir = openeuler_repo_subdir($target);
for my $key (['core', "$fixture/dist/$target/rpms"], ['dep', "$fixture/dep/$subdir"]) {
make_path("$key->[1]/repodata");
write_file("$key->[1]/repodata/repomd.xml.key", 'existing exported key')
unless ($ENV{BUILD_MISSING_KEY} || '') eq $key->[0];
}
}
write_file("$fixture/bin/dnf", "#!/bin/sh\nprintf '%s\\n' \"\$*\" >> \"\$BUILD_FIXTURE/commands\"\nexit \"\${BUILD_DNF_FAILURE:-0}\"\n");
write_file("$fixture/bin/systemctl", "#!/bin/sh\nexit 0\n");
write_file("$fixture/bin/rpmdev-setuptree", "#!/bin/sh\nexit 0\n");
chmod 0755, map {"$fixture/bin/$_"} qw(dnf systemctl rpmdev-setuptree);
local %ENV = (%ENV, HOME => "$fixture/home", BUILD_FIXTURE => $fixture, BUILD_ARCH => $arch, PATH => "$fixture/bin:$ENV{PATH}");
my $pid = fork();
die $! unless defined $pid;
if (!$pid) {
chdir($fixture) or die $!;
open(STDOUT, '>', 'stdout') or die $!;
open(STDERR, '>', 'stderr') or die $!;
exec($^X, 'driver.pl', @args, '--xcat_dep_path', "$fixture/dep") or die $!;
}
waitpid($pid, 0);
my $status = $? >> 8;
my $result = {status => $status, core => read_file("$fixture/repos/xcat-core-local.repo"),
dep => read_file("$fixture/repos/xcat-dep.repo"), commands => read_file("$fixture/commands"), stderr => read_file("$fixture/stderr")};
diag(read_file("$fixture/stderr")) if $status && !$ENV{BUILD_DNF_FAILURE} && !$ENV{BUILD_MISSING_KEY}
&& !$ENV{BUILD_STOP} && !grep {$_ eq '--merge-core-repos'} @args;
return $result;
}
sub write_file {
my ($path, $contents) = @_;
open(my $fh, '>', $path) or die "$path: $!";
print {$fh} $contents;
close($fh) or die $!;
}
sub read_file {
my ($path) = @_;
return '' unless -f $path;
open(my $fh, '<', $path) or die $!;
return do {local $/; scalar <$fh>} || '';
}
-174
View File
@@ -1,174 +0,0 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Path qw(make_path);
use File::Spec;
use File::Temp qw(tempdir);
use FindBin;
use lib "$FindBin::Bin/../lib";
use Test::More;
use XCAT::Test::File qw(repo_path);
my $script = $ENV{XCAT_TEST_GO_XCAT} || repo_path('xCAT-server/share/xcat/tools/go-xcat');
my $root = tempdir(CLEANUP => 1);
my $driver = "$root/driver.sh";
write_file($driver, <<'SH');
source "$GO_XCAT_SOURCE"
awk() {
local -a args=("$@")
local i
for i in "${!args[@]}"; do
[[ "${args[i]}" != /etc/os-release ]] || args[i]="$FIXTURE/os-release"
done
command awk "${args[@]}"
}
dnf() {
if [[ $1 == repoquery ]]; then
printf '%s\n' "${@: -1}"
else
printf 'dnf'; printf ' <%s>' "$@"; printf '\n'
fi
return "${PACKAGE_STATUS:-0}"
}
yum() { printf 'yum'; printf ' <%s>' "$@"; printf '\n'; return "${PACKAGE_STATUS:-0}"; }
download_file() { return 1; }
add_repo_by_file() { cp "$1" "$FIXTURE/$2.repo"; }
TMP_DIR="$FIXTURE"
GO_XCAT_LINUX_DISTRO="$(check_linux_distro)"
GO_XCAT_LINUX_VERSION="$(check_linux_version)"
detected=$?
GO_XCAT_ARCH="$TEST_ARCH"
case "$1" in
detect)
printf '%s\n%s\n' "$GO_XCAT_LINUX_DISTRO" "$GO_XCAT_LINUX_VERSION"
exit "$detected"
;;
repos)
[[ "$detected" = 0 ]] || exit "$detected"
GO_XCAT_DEFAULT_BASE_URL=https://packages.example.test/xcat
add_xcat_core_repo_yum_or_zypper "${CORE_URL:-}" "${REPO_VERSION:-latest}" || exit $?
add_xcat_dep_repo_yum_or_zypper "${DEP_URL:-}" "${REPO_VERSION:-latest}"
;;
install-dnf) install_packages_dnf -y xCAT ;;
install-yum) install_packages_yum -y xCAT ;;
refresh) update_repo_dnf ;;
cleanup-dnf)
type() { [[ "$1" != yum ]] && builtin type "$@"; }
grep() { return 1; }
xargs() { return 0; }
mv() { return 0; }
remove_repo_yum xcat-core
;;
*) exit 64 ;;
esac
SH
my @versions = (
['20.03 (LTS-SP4)', '20.03sp4'],
['22.03 (LTS-SP4)', '22.03sp4'],
['24.03 (LTS-SP1)', '24.03sp1'],
['24.03 (LTS-SP3)', '24.03sp3'],
['24.03 (LTS-SP4)', '24.03sp4'],
['24.03 (LTS)', '24.03'],
);
foreach my $version (@versions) {
foreach my $arch ('x86_64', 'ppc64le') {
my $r = run_case(version => $version->[0], arch => $arch, action => 'repos');
is($r->{status}, 0, "$version->[1] $arch repo creation completes");
like($r->{core}, qr{^baseurl=https://packages\.example\.test/xcat/yum/latest/xcat-core/openeuler\Q$version->[1]\E/\Q$arch\E$}m,
'core repository retains native release, service pack and architecture');
like($r->{dep}, qr{^baseurl=https://packages\.example\.test/xcat/yum/latest/xcat-dep/openeuler\Q$version->[1]\E/\Q$arch\E$}m,
'dependency repository retains native release, service pack and architecture');
}
}
foreach my $id ('openEuler', 'openeuler', "'openEuler'") {
my $r = run_case(id => $id, version => '24.03 (LTS-SP3)', action => 'detect');
is($r->{stdout}, "openeuler\n24.03sp3\n", "$id selects native identity");
}
my $single = run_case(version_line => "VERSION='24.03 (LTS-SP3)'\n", action => 'detect');
is($single->{stdout}, "openeuler\n24.03sp3\n", 'single-quoted VERSION retains SP');
my $ga = run_case(version_line => '', action => 'detect');
is($ga->{stdout}, "openeuler\n24.03\n", 'missing VERSION uses VERSION_ID for GA');
foreach my $version ('24.09', '25.03 (LTS)', '24.03 (LTS-SP0)', '24.03 (LTS-SP04)', '24.03 (LTS-SP3) trailing') {
my $r = run_case(version => $version, action => 'repos');
isnt($r->{status}, 0, "$version is rejected before repo creation");
is($r->{core}, '', 'invalid release creates no core repository');
}
my $wrong_arch = run_case(arch => 'ppc64', action => 'repos');
isnt($wrong_arch->{status}, 0, 'big-endian POWER is not silently admitted');
my $override = run_case(action => 'repos', env => {CORE_URL => 'https://custom.example.test/native-core', DEP_URL => 'https://custom.example.test/native-dep'});
like($override->{core}, qr{^baseurl=https://custom\.example\.test/native-core$}m, 'explicit core URL is authoritative');
like($override->{dep}, qr{^baseurl=https://custom\.example\.test/native-dep/openeuler24\.03sp3/x86_64$}m, 'explicit dependency root preserves the exact native suffix');
my $devel = run_case(action => 'repos', env => {REPO_VERSION => 'devel'});
like($devel->{core}, qr{/yum/devel/core-snap/openeuler24\.03sp3/x86_64$}m, 'development core uses its native subdirectory');
my $offline = "$root/offline";
make_path("$offline/core", "$offline/dep/openeuler24.03sp3/x86_64");
my $local = run_case(action => 'repos', env => {CORE_URL => "file://$offline/core", DEP_URL => "file://$offline/dep"});
is($local->{status}, 0, 'offline native directories are usable');
like($local->{core}, qr/^gpgcheck=1$/m, 'offline native core still requires signed packages');
like($local->{dep}, qr/^gpgcheck=1$/m, 'offline native dependencies still require signed packages');
unlike($local->{dep}, qr/^gpgcheck=0$/m, 'missing metadata signature cannot disable package verification');
foreach my $action ('install-dnf', 'install-yum', 'refresh') {
my $r = run_case(action => $action);
is($r->{status}, 0, "$action completes through its command boundary");
like($r->{stdout}, qr/<--setopt=\*\.gpgcheck=1>/, "$action enforces repository package signatures");
unlike($r->{stdout}, qr/nogpgcheck|strict=0|epel|crb/i, "$action has no native signature or dependency bypass");
if ($action ne 'refresh') {
like($r->{stdout}, qr/<install> <initscripts> <xCAT>/, 'native packages share one transaction');
like($r->{stdout}, qr/<--setopt=strict=1>/, 'native DNF cannot inherit skip-broken configuration') if $action eq 'install-dnf';
my $failed = run_case(action => $action, env => {PACKAGE_STATUS => 37});
is($failed->{status}, 37, 'native dependency/signature failure is returned to the caller');
}
}
foreach my $distro (['rocky', '9.6', 'rh9'], ['rhel', '10.1', 'rh10'], ['sles', '15.6', 'sles15']) {
my $r = run_case(id => $distro->[0], version_id => $distro->[1], action => 'repos');
is($r->{status}, 0, "$distro->[0] repo mapping still completes");
like($r->{core}, qr{/yum/latest/xcat-core$}m, 'existing core layout remains flat');
like($r->{dep}, qr{/xcat-dep/\Q$distro->[2]\E/x86_64$}m, 'existing dependency mapping is preserved');
}
my $el_install = run_case(id => 'rhel', version_id => '10.1', action => 'install-dnf');
is($el_install->{status}, 0, 'EL installation completes after repository checks');
like($el_install->{stdout}, qr/<--nogpgcheck> <--setopt=strict=0>/, 'existing EL install options are unchanged');
my $cleanup = run_case(action => 'cleanup-dnf');
is($cleanup->{status}, 0, 'repository replacement works with DNF and no yum command');
is($cleanup->{stdout}, "dnf <clean> <metadata>\n", 'DNF clears metadata during repository replacement');
done_testing();
sub run_case {
my (%option) = @_;
my $fixture = tempdir(DIR => $root, CLEANUP => 1);
my $version_line = exists($option{version_line}) ? $option{version_line}
: 'VERSION="' . ($option{version} || '24.03 (LTS-SP3)') . "\"\n";
write_file("$fixture/os-release", 'ID=' . ($option{id} || 'openEuler') . "\n" . $version_line .
'VERSION_ID="' . ($option{version_id} || '24.03') . "\"\n");
local %ENV = (%ENV, GO_XCAT_SOURCE => $script, FIXTURE => $fixture,
TEST_ARCH => $option{arch} || 'x86_64', %{$option{env} || {}});
my $pid = fork();
die "fork: $!" unless defined($pid);
if (!$pid) {
open(STDOUT, '>', "$fixture/stdout") or die $!;
open(STDERR, '>', "$fixture/stderr") or die $!;
exec('bash', $driver, $option{action}) or die $!;
}
waitpid($pid, 0);
my $status = $? >> 8;
return {status => $status, stdout => read_file("$fixture/stdout"), stderr => read_file("$fixture/stderr"),
core => read_file("$fixture/xcat-core.repo"), dep => read_file("$fixture/xcat-dep.repo")};
}
sub write_file {
my ($path, $contents) = @_;
open(my $fh, '>', $path) or die "$path: $!";
print {$fh} $contents;
close($fh) or die "$path: $!";
}
sub read_file {
my ($path) = @_;
return '' unless -f $path;
open(my $fh, '<', $path) or die "$path: $!";
return do {local $/; scalar <$fh>} || '';
}
+1 -1
View File
@@ -32,7 +32,7 @@ my ($help, $load_error);
is(ref($load_error), 'MysqlSetupTestExit', 'the complete CLI reaches its help exit');
is(ref($load_error) ? $load_error->{status} : undef, 0, 'help exits successfully');
like($help, qr/mysqlsetup/, 'help prints the CLI usage');
BAIL_OUT("Unable to load complete mysqlsetup: $load_error")
die("Unable to load complete mysqlsetup: $load_error")
unless ref($load_error) eq 'MysqlSetupTestExit' && $load_error->{status} == 0;
my $service_map = \&xCAT::Utils::servicemap;
@@ -154,13 +154,7 @@ for my $failure (qw(none kernel-copy dracut move-initrd)) {
is($state->{lock_calls}, 0, "EL $failure retains its existing onlyinitrd lock behavior");
}
my @legacy_repositories = qw(
oracle-linux-ol8.repo oracle-linux-ol9.repo oracle-linux-ol10.repo
uek-ol8.repo uek-ol9.repo uek-ol10.repo Rocky-AppStream.repo Rocky-BaseOS.repo
Rocky-Extras.repo rocky.repo rocky-extras.repo CentOS-Base.repo centos.repo
centos-addons.repo almalinux-ha.repo almalinux-nfv.repo almalinux-plus.repo
almalinux-powertools.repo almalinux.repo almalinux-resilientstorage.repo almalinux-rt.repo
);
my @legacy_repositories = qw(Rocky-BaseOS.repo almalinux.repo CentOS-Base.repo oracle-linux-ol10.repo);
my @custom_repositories = qw(local-repository-0.repo xCAT-custom.repo administrator.repo);
my $enabled = "[fixture]\nenabled=1\ngpgcheck=1\nbaseurl=https://repo.invalid/\n";
my $disabled = "[fixture]\nenabled=0\ngpgcheck=1\nbaseurl=https://repo.invalid/\n";
+19 -20
View File
@@ -57,25 +57,25 @@ for my $name (qw(uname blockdev udevadm mknod chown vgs vgchange vgremove logger
my $small = 62914560;
my $large = 8589934592;
my @cases = (
['native BIOS small', 'openeuler', 'x86_64', 0, $small, 1, 0, 0],
['native BIOS large', 'openeuler', 'x86_64', 0, $large, 1, 0, 0],
['native UEFI small', 'openeuler', 'x86_64', 1, $small, 0, 1, 0],
['native UEFI large', 'openeuler', 'x86_64', 1, $large, 0, 1, 0],
['native POWER small', 'openeuler', 'ppc64le', 0, $small, 0, 0, 1],
['native POWER large', 'openeuler', 'ppc64le', 0, $large, 0, 0, 1],
['legacy BIOS small', 'rhels8', 'x86_64', 0, $small, 0, 0, 0],
['legacy BIOS large', 'rhels8', 'x86_64', 0, $large, 1, 0, 0],
['legacy UEFI small', 'rhels8', 'x86_64', 1, $small, 0, 1, 0],
['legacy UEFI large', 'rhels8', 'x86_64', 1, $large, 1, 1, 0],
['legacy POWER large', 'rhels8', 'ppc64le', 0, $large, 1, 0, 1],
['legacy POWER big endian', 'rhels8', 'ppc64', 0, $small, 0, 0, 1],
['native BIOS static custom', 'openeuler', 'x86_64', 0, $small, 0, 0, 0, 'static'],
['native BIOS script custom', 'openeuler', 'x86_64', 0, $small, 0, 0, 0, 'script'],
['native UEFI custom', 'openeuler', 'x86_64', 1, $small, 0, 0, 0, 'static'],
['native POWER custom', 'openeuler', 'ppc64le', 0, $large, 0, 0, 0, 'static'],
['native BIOS small', 'openeuler', 'x86_64', 0, $small, 1, 0, 0, 'xfs'],
['native BIOS large', 'openeuler', 'x86_64', 0, $large, 1, 0, 0, 'xfs'],
['native UEFI small', 'openeuler', 'x86_64', 1, $small, 0, 1, 0, 'xfs'],
['native UEFI large', 'openeuler', 'x86_64', 1, $large, 0, 1, 0, 'xfs'],
['native POWER small', 'openeuler', 'ppc64le', 0, $small, 0, 0, 1, 'ext4'],
['native POWER large', 'openeuler', 'ppc64le', 0, $large, 0, 0, 1, 'ext4'],
['legacy BIOS small', 'rhels8', 'x86_64', 0, $small, 0, 0, 0, 'xfs'],
['legacy BIOS large', 'rhels8', 'x86_64', 0, $large, 1, 0, 0, 'xfs'],
['legacy UEFI small', 'rhels8', 'x86_64', 1, $small, 0, 1, 0, 'xfs'],
['legacy UEFI large', 'rhels8', 'x86_64', 1, $large, 1, 1, 0, 'xfs'],
['legacy POWER large', 'rhels8', 'ppc64le', 0, $large, 1, 0, 1, 'xfs'],
['legacy POWER big endian', 'rhels8', 'ppc64', 0, $small, 0, 0, 1, 'xfs'],
['native BIOS static custom', 'openeuler', 'x86_64', 0, $small, 0, 0, 0, 'xfs', 'static'],
['native BIOS script custom', 'openeuler', 'x86_64', 0, $small, 0, 0, 0, 'xfs', 'script'],
['native UEFI custom', 'openeuler', 'x86_64', 1, $small, 0, 0, 0, 'xfs', 'static'],
['native POWER custom', 'openeuler', 'ppc64le', 0, $large, 0, 0, 0, 'ext4', 'static'],
);
for my $case (@cases) {
my ($label, $platform, $arch, $efi, $sectors, $bios, $esp, $prep, $custom) = @$case;
my ($label, $platform, $arch, $efi, $sectors, $bios, $esp, $prep, $fstype, $custom) = @$case;
my $fixture = tempdir(DIR => $tmp, CLEANUP => 1);
make_path(map { "$fixture/$_" } qw(tmp dev proc sys/firmware var/log/xcat etc));
make_path("$fixture/sys/firmware/efi") if $efi;
@@ -103,7 +103,7 @@ for my $case (@cases) {
my $rendered = read_file("$fixture/output.ks");
my ($pre) = $rendered =~ /^%pre[^\n]*\n(.*?)^%end/ms;
defined($pre) or die 'rendered Kickstart did not contain a pre-install script';
$pre =~ s{(/(?:tmp|dev|proc|sys|etc|var)(?=/|\b)|/foo\.log)}{$fixture$1}g;
$pre =~ s{(?<![[:alnum:]_/:])(/(?:tmp|dev|proc|sys|etc|var)(?=/|\b)|/foo\.log)}{$fixture$1}g;
$pre =~ s{/(?:usr/bin/python3|usr/libexec/platform-python)}{$tmp/bin/python3}g;
write_file("$fixture/pre.sh", $pre);
my ($rc, $output);
@@ -114,7 +114,7 @@ for my $case (@cases) {
open(my $pipe, '-|', 'sh', '-c', 'exec "$@" 2>&1', 'sh', '/bin/bash', "$fixture/pre.sh") or die $!;
$output = do {local $/; <$pipe>};
close($pipe);
$rc = $? >> 8;
$rc = (($? & 127) ? 128 + ($? & 127) : $? >> 8);
}
is($rc, 0, "$label executes the complete generated pre-install script") or diag($output);
my $layout = read_file("$fixture/tmp/partitionfile");
@@ -129,7 +129,6 @@ for my $case (@cases) {
my $disk = "$fixture/dev/vda";
like($layout, qr/^ignoredisk --only-use=\Q$disk\E$/m, "$label retains actual selected disk");
like($layout, qr/^part biosboot --ondisk=\Q$disk\E --size=1$/m, "$label reserves one MiB on the selected disk") if $bios;
my $fstype = $platform eq 'openeuler' && $arch eq 'ppc64le' ? 'ext4' : 'xfs';
like($layout, qr{^part /boot --fstype=$fstype }m, "$label retains boot filesystem policy");
like($layout, qr{^logvol / --fstype=$fstype }m, "$label retains root filesystem policy");
like($layout, qr/^bootloader --boot-drive=vda$/m, "$label retains selected boot drive");
+11 -6
View File
@@ -21,13 +21,18 @@ sub asset {
return $path;
}
foreach my $version (qw(20.03sp4 22.03sp4 24.03sp1 24.03sp3 24.03sp4 24.03)) {
my $os = "openeuler$version";
my ($base) = $os =~ /^(openeuler[0-9]+\.03)/;
my @expected = $os eq $base ? ($os, 'openeuler') : ($os, $base, 'openeuler');
is_deeply([xCAT::SvrUtils::get_os_search_list($os)], \@expected,
for my $case (
['openeuler20.03sp4', [qw(openeuler20.03sp4 openeuler20.03 openeuler)], '20', '03sp4'],
['openeuler22.03sp4', [qw(openeuler22.03sp4 openeuler22.03 openeuler)], '22', '03sp4'],
['openeuler24.03sp1', [qw(openeuler24.03sp1 openeuler24.03 openeuler)], '24', '03sp1'],
['openeuler24.03sp3', [qw(openeuler24.03sp3 openeuler24.03 openeuler)], '24', '03sp3'],
['openeuler24.03sp4', [qw(openeuler24.03sp4 openeuler24.03 openeuler)], '24', '03sp4'],
['openeuler24.03', [qw(openeuler24.03 openeuler)], '24', '03'],
) {
my ($os, $expected, $major, $minor) = @$case;
my $base = "openeuler$major.03";
is_deeply([xCAT::SvrUtils::get_os_search_list($os)], $expected,
"$os falls back within its LTS family without an earlier SP");
my ($major, $minor) = split /\./, $version, 2;
is_deeply([xCAT::SvrUtils::parseosver($os)], ['openeuler', $major, $minor],
"$os keeps the service pack in osdistro version fields");
is(xCAT::SvrUtils->getplatform($os), 'openeuler', "$os uses native platform assets");
@@ -15,6 +15,7 @@ use XCAT::Test::File qw(repo_path);
plan skip_all => 'requires Linux cpio, gzip and tar'
unless $^O eq 'linux' && -x '/usr/bin/cpio' && -x '/usr/bin/gzip' && -x '/usr/bin/tar';
local $ENV{PATH} = "$ENV{PATH}:/usr/sbin:/sbin";
my $dir = tempdir(CLEANUP => 1);
make_path("$dir/db", "$dir/root/lib/perl", "$dir/root/share", "$dir/install/postscripts");
symlink(repo_path('perl-xCAT/xCAT'), "$dir/root/lib/perl/xCAT") or die $!;
+1 -1
View File
@@ -43,7 +43,7 @@ my ($help, $load_error);
is(ref($load_error), 'PgSetupTestExit', 'the complete CLI reaches its help exit');
is(ref($load_error) ? $load_error->{status} : undef, 0, 'help exits successfully');
like($help, qr/pgsqlsetup/, 'help prints usage');
BAIL_OUT("Unable to load complete pgsqlsetup: $load_error")
die("Unable to load complete pgsqlsetup: $load_error")
unless ref($load_error) eq 'PgSetupTestExit' && $load_error->{status} == 0;
sub invoke {
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/../lib";
use Test::More;
use XCAT::Test::File qw(repo_path);
plan skip_all => 'requires rpmspec to render package conditionals'
if system('sh', '-c', 'command -v rpmspec >/dev/null 2>&1');
my @macros = ('--define', 'version 2.18.0', '--define', 'release 1',
'--undefine', 'rhel', '--undefine', 'fedora', '--undefine', 'suse_version', '--undefine', 'openEuler');
my @roles = (['mn', repo_path('xCAT/xCAT.spec')], ['sn', repo_path('xCATsn/xCATsn.spec')]);
for my $platform (['openEuler', 2, 'apach24'], ['rhel', 9, 'apach24'],
['fedora', 40, 'apach24'], ['suse_version', 1500, 'apach24'],
['rhel', 6, 'apach22']) {
my ($macro, $value, $generation) = @$platform;
for my $role (@roles) {
open(my $pipe, '-|', 'rpmspec', '-P', @macros,
'--define', "$macro $value", $role->[1]) or die "rpmspec: $!";
my $rendered = do { local $/; <$pipe> };
close($pipe) or die "Cannot render $role->[0] for $macro: $?";
my ($install) = $rendered =~ /^%install\s*\n(.*?)(?=^%[a-z]|\z)/msg;
die 'Rendered spec has no install section' unless defined($install);
my $source = $generation eq 'apach24' ? 'xcat.conf.apach24' : 'xcat.conf';
for my $directory (qw(httpd apache2)) {
like($install, qr{^cp\s+\S*/\Q$source\E\s+\$RPM_BUILD_ROOT/etc/$directory/conf\.d/xcat\.conf\s*$}m,
"$role->[0] $macro $value installs $generation for $directory");
}
}
}
done_testing();
@@ -72,23 +72,6 @@ for my $pkg (@packages) {
"$label package still takes the Apache 2.4 configuration from Source$apache24" );
my $install = section( $spec, 'install' );
like(
$install,
qr{
^%if \s+ 0%\{\?fedora\} \s* \|\| \s*
0%\{\?rhel\} \s* >= \s* 7 \s* \|\| \s*
0%\{\?suse_version\} \s* >= \s* 1200 \s* \|\| \s*
0%\{\?openEuler\} \s* $ \n
^cp \s+ %\{SOURCE$apache24\} \s+ \$RPM_BUILD_ROOT/etc/$first/conf\.d/xcat\.conf \s* $ \n
^cp \s+ %\{SOURCE$apache24\} \s+ \$RPM_BUILD_ROOT/etc/$second/conf\.d/xcat\.conf \s* $ \n
^%else \s* $ \n
^cp \s+ %\{SOURCE1\} \s+ \$RPM_BUILD_ROOT/etc/$first/conf\.d/xcat\.conf \s* $ \n
^cp \s+ %\{SOURCE1\} \s+ \$RPM_BUILD_ROOT/etc/$second/conf\.d/xcat\.conf \s* $ \n
^%endif \s* $
}mx,
"$label package selects Apache 2.4 at build time on Fedora, EL7+, SLES 12+ and openEuler"
);
# The next upgrade's %pretrans compares against these, so they must stay shipped.
like( $install, qr{^cp \s+ %\{SOURCE$apache24\} \s+ \$RPM_BUILD_ROOT$templates/xcat\.conf\.apach24 \s* $}mx,
"$label package still saves the Apache 2.4 template under conf.orig" );