mirror of
https://github.com/xcat2/xcat-dep.git
synced 2026-09-30 23:05:17 +00:00
Merge master into fix/riscv64-net-dns-key-record
Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
This commit is contained in:
@@ -35,6 +35,10 @@ jobs:
|
||||
perl -c mockbuild-all.pl
|
||||
perl -c BuildUtils.pm
|
||||
perl -c sbuild-all.pl
|
||||
perl -c ipxe-xcat/mockbuild.pl
|
||||
perl -c ipxe-xcat/sbuild.pl
|
||||
perl -c ipxe-xcat/verify-payload.pl
|
||||
rpmspec -P ipxe-xcat/ipxe-xcat.spec >/dev/null
|
||||
rpmspec -P \
|
||||
-D 'genesis_arch x86_64' \
|
||||
-D 'version 2.19.0' \
|
||||
@@ -44,6 +48,9 @@ jobs:
|
||||
genesis-openembedded/build \
|
||||
genesis-openembedded/package \
|
||||
genesis-openembedded/verify-release \
|
||||
ipxe-xcat/mockbuild.pl \
|
||||
ipxe-xcat/sbuild.pl \
|
||||
ipxe-xcat/verify-payload.pl \
|
||||
lib/XCAT/BuildUtils.pm \
|
||||
lib/XCAT/GenesisRelease.pm \
|
||||
mockbuild-all.pl \
|
||||
@@ -51,6 +58,7 @@ jobs:
|
||||
t/common-repo-gate.t \
|
||||
t/genesis_openembedded_release.t \
|
||||
t/genesis_openembedded_consumer.t \
|
||||
t/ipxe_xcat_payload.t \
|
||||
t/lib/XCAT/GenesisReleaseTest.pm
|
||||
|
||||
- name: Run package tests
|
||||
@@ -58,8 +66,12 @@ jobs:
|
||||
prove -v t/build_utils.t
|
||||
prove -v t/build_timeout.t
|
||||
prove -v t/sbuild-all.t
|
||||
prove -v t/goconserver_cross_build.t
|
||||
prove -v t/ipxe_xcat_payload.t
|
||||
prove -v t/mockbuild-all.t
|
||||
prove -v t/net_dns_rr_types.t
|
||||
prove -v t/repo-lock-race.t
|
||||
prove -v t/nfslock.t
|
||||
prove -v -It/lib t/genesis_openembedded_release.t
|
||||
sudo -E prove -v -It/lib t/genesis_openembedded_consumer.t
|
||||
prove -v t/riscv64_perl_cell.t
|
||||
|
||||
@@ -50,6 +50,7 @@ This guide uses the following placeholders consistently:
|
||||
- `<REPO_ROOT>/goconserver/mockbuild.pl`
|
||||
- `<REPO_ROOT>/conserver/mockbuild.pl`
|
||||
- `<REPO_ROOT>/xnba/mockbuild.pl`
|
||||
- `<REPO_ROOT>/ipxe-xcat/mockbuild.pl`
|
||||
- `<REPO_ROOT>/mockbuild-perl-packages.pl`
|
||||
- `<XCAT_SOURCE>/buildrpms.pl` — only to build the OS-dependent `xCAT-genesis-base` package (unless `--skip-genesis` is set); the full xCAT core is built separately by the xcat-core pipeline, not here.
|
||||
|
||||
@@ -103,7 +104,7 @@ Use these flags to skip specific operations:
|
||||
- `--skip-genesis`
|
||||
- Skips the `xCAT-genesis-base` build (`<XCAT_SOURCE>/buildrpms.pl --package xCAT-genesis-base`).
|
||||
- `--skip-xcat-dep`
|
||||
- Skips non-perl xcat-dep package builders (`elilo`, `grub2-xcat`, `ipmitool-xcat`, `syslinux-xcat`, `goconserver`, `conserver-xcat`, `xnba-undi`).
|
||||
- Skips non-perl xcat-dep package builders (`elilo`, `grub2-xcat`, `ipmitool-xcat`, `syslinux-xcat`, `goconserver`, `conserver-xcat`, `xnba-undi`, `ipxe-xcat`).
|
||||
- `--skip-perl`
|
||||
- Skips `<REPO_ROOT>/mockbuild-perl-packages.pl`.
|
||||
- With any of the three flags above, the run repository, the tarball and the deployed cell keep
|
||||
@@ -138,8 +139,10 @@ Use these flags to skip specific operations:
|
||||
- Adds extra artifact roots to the collection phase (repeatable).
|
||||
- `--dry-run`
|
||||
- Prints planned actions without executing them.
|
||||
- `--force-unlock`
|
||||
- Removes a stale lock after the previous publisher has been checked.
|
||||
- `--try-unlock-timeout <N>`
|
||||
- Waits about N seconds for a lock that a live process holds, in retries of 3 seconds with
|
||||
at least one retry, then fails and prints the command that removes the lock. A lock whose
|
||||
owner is proven dead on this host is taken over at once.
|
||||
|
||||
# Prerequisites
|
||||
|
||||
@@ -245,12 +248,19 @@ published once under `xcat-dep/common`. Source RPMs stay in the verified
|
||||
release directory. Existing per-EL repositories keep the old Genesis packages
|
||||
and contain no OpenEmbedded copies.
|
||||
|
||||
The build holds separate locks for its work area and the published repository.
|
||||
It prepares the complete common repository in a temporary directory, then
|
||||
The build locks its work area (`<output>/.lock`), each repository cell it deploys
|
||||
(`<repo-dep>/rh<N>/.<arch>.lock`) and, while it publishes, the common repository
|
||||
(`<repo-dep>/.common-publish.lock`). The per-arch runs of one build lock different
|
||||
cells, so they run in parallel. A lock whose owner is dead is taken over only on the
|
||||
owner's host. From any other host the build waits `--try-unlock-timeout` seconds,
|
||||
then fails with the command that removes the lock. The protocol is documented at the
|
||||
top of `lib/XCAT/NFSLock.pm`.
|
||||
|
||||
The build prepares the complete common repository in a temporary directory, then
|
||||
replaces the previous repository only after package verification, metadata
|
||||
generation, and signing have succeeded. If a stopped publisher leaves staging
|
||||
or backup directories behind, rerun it with ``--force-unlock`` to recover the
|
||||
previous repository before starting a new publication.
|
||||
or backup directories behind, the next run recovers the previous repository
|
||||
when no other run holds the common lock.
|
||||
|
||||
Repository publication requires all eight current architectures. Version 1
|
||||
release manifests remain readable, but they cannot replace the current
|
||||
@@ -459,7 +469,7 @@ missing, and then builds the `[rocky-10-riscv64-xcat]` section of `packages-mani
|
||||
| goconserver | cross-compiled on the host (`GOARCH=riscv64`), packaged with `rpmbuild --target riscv64` |
|
||||
| grub2-xcat (noarch) | built in the native, EPEL-free `rocky-10-x86_64` chroot |
|
||||
| perl list6 + EPEL gap (`--epel-gap`) | `mockbuild-perl-packages.pl --target-arch riscv64 --noarch-mock-cfg rocky-10-x86_64 --epel-gap`: XS modules in the riscv64 chroot, noarch modules in the native chroot |
|
||||
| elilo-xcat, syslinux-xcat, xnba-undi (noarch) | built in the native `rocky-10-x86_64` chroot, like grub2-xcat: a riscv64 management node serves the x86 nodes of a mixed cluster. The target is cross-built on x86_64 only, as its mock config states |
|
||||
| elilo-xcat, ipxe-xcat, syslinux-xcat, xnba-undi (noarch) | built in the native `rocky-10-x86_64` chroot, like grub2-xcat: a riscv64 management node serves the x86 nodes of a mixed cluster. The target is cross-built on x86_64 only, as its mock config states |
|
||||
|
||||
There is no EPEL for riscv64, so the perl deps of xCAT that EL10 otherwise takes from EPEL
|
||||
are built here as well (`--epel-gap` in `mockbuild-perl-packages.pl`: perl-Crypt-Blowfish,
|
||||
@@ -611,16 +621,16 @@ Codename ↔ version (the single supported set — `BuildUtils` is the source of
|
||||
`mk-build-deps`, so version constraints, `a | b` alternatives and arch qualifiers are honoured) are
|
||||
all **fatal** on failure — and since nothing survives the session, a package whose `debian/control`
|
||||
forgets a `Build-Depends` cannot build green on a sibling package's leftovers.
|
||||
- **Per-arch package sets (`debs-manifest.conf`).** One `[<codename>-<arch>]` section per target. The
|
||||
noarch boot components (`syslinux-xcat`/`grub2-xcat`/`elilo-xcat`/`xnba-undi`, `Architecture:all`)
|
||||
are built ONCE on amd64 — single producer, their source is x86-only — and assembled into every
|
||||
arch's `Packages` index. They are listed for **ppc64el too, as required-present**, so the gate
|
||||
verifies the ppc repo actually carries them (a ppc MN needs them for netboot, matching the EL
|
||||
manifest). `build_one_codename` **skips** an `Architecture:all` package on any non-amd64 arch
|
||||
(detected via `control_binary_arch`), so ppc64el and riscv64 build only the genuinely
|
||||
arch-specific compiled deps (`ipmitool-xcat`, `conserver-xcat`, `goconserver`) yet still verify the
|
||||
boot components they need. The riscv64 sections require the same four boot components as
|
||||
ppc64el: a riscv64 management node serves the x86 nodes of a mixed cluster.
|
||||
- **Per-arch package sets (`debs-manifest.conf`).** One `[<codename>-<arch>]` section per target.
|
||||
The noarch boot components (`syslinux-xcat`/`grub2-xcat`/`elilo-xcat`/`xnba-undi`/`ipxe-xcat`,
|
||||
`Architecture:all`) are built ONCE on amd64 — single producer, most of them from x86-only source —
|
||||
and assembled into every arch's `Packages` index. They are listed for **ppc64el too, as
|
||||
required-present**, so the gate verifies the ppc repo actually carries them (a ppc MN needs them
|
||||
for netboot, matching the EL manifest). `build_one_codename` **skips** an `Architecture:all`
|
||||
package on any non-amd64 arch (detected via `control_binary_arch`), so ppc64el and riscv64 build
|
||||
only the genuinely arch-specific compiled deps (`ipmitool-xcat`, `conserver-xcat`, `goconserver`)
|
||||
yet still verify the boot components they need. The riscv64 sections require the same five boot
|
||||
components as ppc64el: a riscv64 management node serves the x86 nodes of a mixed cluster.
|
||||
- **Fail-hard.** Any required chroot / package / artifact failure, or any version-pin mismatch, fails
|
||||
the whole run non-zero.
|
||||
- **Genesis keeps its maintained packaging.** A native `xcat-genesis-base` deb is INGESTED as-is when
|
||||
@@ -686,7 +696,7 @@ needs no `--mirror`.
|
||||
| ipmitool-xcat, conserver-xcat | `dpkg-buildpackage` in the emulated riscv64 chroot |
|
||||
| goconserver | same chroot, compiled by the Go toolchain the chroot installs for riscv64 |
|
||||
| grub2-xcat (`Architecture:all`) | built once on amd64 and assembled into the riscv64 index; listed in the riscv64 manifest sections as required-present, because a riscv64 management node needs it to netboot |
|
||||
| syslinux-xcat, elilo-xcat, xnba-undi (`Architecture:all`) | built once on amd64 and assembled into the riscv64 index; required-present like grub2-xcat, because a riscv64 management node serves the x86 nodes of a mixed cluster |
|
||||
| syslinux-xcat, elilo-xcat, xnba-undi, ipxe-xcat (`Architecture:all`) | built once on amd64 and assembled into the riscv64 index; required-present like grub2-xcat, because a riscv64 management node serves the x86 nodes of a mixed cluster |
|
||||
| xcat-genesis-base | not built: no riscv64 section names it, and the build skips the step when the manifest does not ask for it, so `--skip-genesis` is unnecessary here |
|
||||
|
||||
The riscv64 ipmitool-xcat deb is installed into the chroot that built it and
|
||||
|
||||
+24
-50
@@ -37,7 +37,7 @@ our @EXPORT_OK = qw(
|
||||
supported_arches is_supported_arch
|
||||
chroot_name chroot_sources_list chroot_is_disposable chroot_build_script
|
||||
chroot_build_timeout
|
||||
control_field genesis_deb_control
|
||||
control_field genesis_debs_for_codename
|
||||
deb_field deb_version deb_hash cross_copy_genesis_deb
|
||||
build_deb_in_chroot
|
||||
);
|
||||
@@ -476,53 +476,6 @@ sub control_field {
|
||||
return undef;
|
||||
}
|
||||
|
||||
# genesis_deb_control: build the DEBIAN/control text for the cross-arch-converted xcat-genesis-base
|
||||
# deb, PRESERVING the maintained packaging's semantics (Depends/Breaks/Replaces/Section/Priority)
|
||||
# instead of hand-rolling a bare 5-field control (the bug in build-dep-debs.sh flagged by review
|
||||
# concern #2). $maintained is the text of xCAT-genesis-builder/debian/control (or undef when it
|
||||
# cannot be located — then a minimal-but-honest control is produced and the caller should warn).
|
||||
# $pkgname is e.g. xcat-genesis-base-ppc64el, $version the deb version, $arch 'all'. Pure/testable.
|
||||
sub genesis_deb_control {
|
||||
my ($maintained, $pkgname, $version, $arch) = @_;
|
||||
$arch ||= 'all';
|
||||
my %f = (
|
||||
Package => $pkgname,
|
||||
Version => $version,
|
||||
Architecture => $arch,
|
||||
Section => 'admin',
|
||||
Priority => 'optional',
|
||||
Maintainer => 'xCAT <xcat-user@lists.sourceforge.net>',
|
||||
);
|
||||
if (defined $maintained && $maintained ne '') {
|
||||
for my $k (qw(Section Priority Maintainer Depends Pre-Depends Recommends
|
||||
Suggests Breaks Replaces Conflicts Provides)) {
|
||||
my $v = control_field($maintained, $k);
|
||||
$f{$k} = $v if defined $v && $v ne '';
|
||||
}
|
||||
my $desc = control_field($maintained, 'Description');
|
||||
$f{Description} = $desc if defined $desc && $desc ne '';
|
||||
}
|
||||
$f{Description} ||= 'xCAT Genesis netboot image (converted from the rpm for cross-arch netboot)';
|
||||
# ${misc:Depends} is a debhelper substitution var that only resolves during a real dpkg build;
|
||||
# in a hand-assembled control it would ship literally, so drop it from a preserved Depends.
|
||||
for my $k (qw(Depends Pre-Depends Recommends Suggests)) {
|
||||
next unless defined $f{$k};
|
||||
$f{$k} =~ s/\$\{[^}]+\}//g;
|
||||
$f{$k} =~ s/^[,\s]+|[,\s]+$//g;
|
||||
$f{$k} =~ s/\s*,\s*,\s*/, /g;
|
||||
delete $f{$k} if $f{$k} eq '';
|
||||
}
|
||||
my @order = qw(Package Version Section Priority Architecture Maintainer
|
||||
Pre-Depends Depends Recommends Suggests Breaks Replaces Conflicts
|
||||
Provides Description);
|
||||
my $out = '';
|
||||
for my $k (@order) {
|
||||
next unless defined $f{$k} && $f{$k} ne '';
|
||||
$out .= "$k: $f{$k}\n";
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------------------------------
|
||||
# Built-.deb inspection + cross-arch genesis provisioning (filesystem; tested with real dpkg-deb).
|
||||
# ---------------------------------------------------------------------------------------------------
|
||||
@@ -596,9 +549,30 @@ sub deb_hash {
|
||||
# Idempotent; content is compared by deb_hash so a stale same-name deb is refreshed rather than
|
||||
# mistaken for up to date. $sign is an optional coderef($deb_path) invoked on each copied deb; pass
|
||||
# undef to skip. Mirrors MockBuildUtils::cross_copy_genesis for the apt world.
|
||||
# $codename, when given, narrows the set to the image built for that release -- see
|
||||
# genesis_debs_for_codename.
|
||||
# genesis_debs_for_codename: the Genesis debs that belong to ONE Ubuntu release.
|
||||
#
|
||||
# The Genesis image carries the kernel of the root that built it, so xcat-core builds one deb per
|
||||
# codename and stamps the codename into the version (2.19.0-snap...~noble). Staging all of them into
|
||||
# every suite publishes three images per suite, and apt serves the newest -- the image of another
|
||||
# release. A deb with no codename in its version predates the native build and serves every release.
|
||||
sub genesis_debs_for_codename {
|
||||
my ($debs, $codename) = @_;
|
||||
my @debs = @{ $debs || [] };
|
||||
return @debs unless @debs && defined $codename && $codename ne '';
|
||||
my $marked = qr/_[^_]*~[A-Za-z0-9.]+_[^_]*\.deb\z/;
|
||||
return @debs unless grep { basename($_) =~ $marked } @debs;
|
||||
return grep {
|
||||
my $base = basename($_);
|
||||
$base =~ /_[^_]*~\Q$codename\E_[^_]*\.deb\z/ || $base !~ $marked;
|
||||
} @debs;
|
||||
}
|
||||
|
||||
sub cross_copy_genesis_deb {
|
||||
my ($from, $to, $arch, $sign) = @_;
|
||||
my @src = glob("$from/xcat-genesis-base-$arch\_*.deb");
|
||||
my ($from, $to, $arch, $sign, $codename) = @_;
|
||||
my @src = genesis_debs_for_codename(
|
||||
[ glob("$from/xcat-genesis-base-$arch\_*.deb") ], $codename);
|
||||
return 0 if !@src;
|
||||
my %want = map { basename($_) => $_ } @src;
|
||||
my @existing = glob("$to/xcat-genesis-base-$arch\_*.deb");
|
||||
|
||||
+91
-4
@@ -6,10 +6,13 @@ package MockBuildUtils;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
use File::Basename qw(basename);
|
||||
use File::Basename qw(basename dirname);
|
||||
use File::Copy qw(copy);
|
||||
use File::Glob qw(bsd_glob);
|
||||
use File::Find;
|
||||
use File::Path qw(remove_tree);
|
||||
use lib dirname(__FILE__) . '/lib';
|
||||
use XCAT::NFSLock ();
|
||||
use Sys::Hostname;
|
||||
use Digest::MD5 qw(md5_hex);
|
||||
|
||||
@@ -23,7 +26,8 @@ our @EXPORT_OK = qw(
|
||||
parse_evr evr_cmp evr_constraint_ok parse_pin rpmkeys_checksig_problem
|
||||
rpm_version rpm_release rpm_sigmd5 rpm_is_signed restamp_release_line
|
||||
cross_copy_genesis finalize_xcat_dep bump_dep_release_suffix
|
||||
build_mock_uniqueext rpm_in_cell
|
||||
build_mock_uniqueext rpm_in_cell resolve_mock_cfg
|
||||
recover_common_repository
|
||||
);
|
||||
|
||||
# install_deps_packages($os_id): the host packages mockbuild-all.pl needs to run at all, for the
|
||||
@@ -545,6 +549,15 @@ sub finalize_xcat_dep {
|
||||
my ($x86_64_repo, $ppc64le_repo, %opt) = @_;
|
||||
my $sign = $opt{sign};
|
||||
my $reindex = $opt{reindex};
|
||||
# The arches whose cells this run writes. The others are read only: each host finalizes the
|
||||
# cells it deploys, so it never holds a cell lock that only another host could reclaim.
|
||||
my %known = map { $_->{arch} => 1 } @GENESIS_ARCHES;
|
||||
my @only = @{ $opt{only} // [ map { $_->{arch} } @GENESIS_ARCHES ] };
|
||||
for my $a (@only) {
|
||||
die "FATAL: [finalize] no cross-arch genesis for arch '$a'\n" unless $known{$a};
|
||||
}
|
||||
my %write = map { $_ => 1 } @only;
|
||||
my @dst_arches = grep { $write{ $_->{arch} } } @GENESIS_ARCHES;
|
||||
print_step('Finalize xcat-dep: cross-arch genesis-base provisioning (issue #7610)');
|
||||
print "x86_64-repo: $x86_64_repo\n";
|
||||
print "ppc64le-repo: $ppc64le_repo\n";
|
||||
@@ -585,7 +598,7 @@ sub finalize_xcat_dep {
|
||||
# N-way cross-copy: put each arch's genesis into EVERY other arch's repo dir.
|
||||
my @summary;
|
||||
for my $src (@GENESIS_ARCHES) {
|
||||
for my $dst (@GENESIS_ARCHES) {
|
||||
for my $dst (@dst_arches) {
|
||||
next if $src->{arch} eq $dst->{arch};
|
||||
my $n = cross_copy_genesis($adir{$src->{arch}}, $adir{$dst->{arch}}, $src->{tarch}, $sign);
|
||||
push @summary, "$n $src->{tarch} -> $dst->{arch}";
|
||||
@@ -596,7 +609,7 @@ sub finalize_xcat_dep {
|
||||
# rpm on disk (so cross_copy_genesis now returns 0) yet ABSENT from repomd.xml -- which no
|
||||
# signature gate catches. Re-indexing is cheap (tiny repos) and idempotent, and heals that
|
||||
# partial state; skipped only when no signer/indexer was injected.
|
||||
if ($reindex) { $reindex->($adir{$_->{arch}}) for @GENESIS_ARCHES; }
|
||||
if ($reindex) { $reindex->($adir{$_->{arch}}) for @dst_arches; }
|
||||
print "[finalize] $osdir: " . join(', ', @summary) . "\n";
|
||||
$pairs++;
|
||||
}
|
||||
@@ -723,4 +736,78 @@ sub build_mock_uniqueext {
|
||||
return sprintf("mba-%02d-%s-%s", $idx, $run_part, $label_part);
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
|
||||
=head3 recover_common_repository
|
||||
|
||||
Descriptions:
|
||||
Put back the common tree that an interrupted publication moved aside, and
|
||||
remove the staging trees it left. Runs only under the common lock, so it
|
||||
never removes the staging tree of a run that is still publishing.
|
||||
Arguments:
|
||||
$base: the --repo-dep directory
|
||||
Returns:
|
||||
1 when the recovery ran, 0 when another run holds the common lock.
|
||||
|
||||
=cut
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
sub recover_common_repository {
|
||||
my ($base) = @_;
|
||||
my $path = "$base/.common-publish.lock";
|
||||
my $lock = eval { XCAT::NFSLock->acquire($path, label => 'common lock') };
|
||||
unless ($lock) {
|
||||
# The lock is live or unproven: this is not the place to offer its removal.
|
||||
print "common recovery skipped: another run holds $path\n";
|
||||
return 0;
|
||||
}
|
||||
my $destination = "$base/common";
|
||||
my @backups = sort {
|
||||
((stat($a))[9] // 0) <=> ((stat($b))[9] // 0)
|
||||
} grep { -d $_ && !-l $_ } bsd_glob("$base/.common.previous.*");
|
||||
|
||||
if (!-e $destination && !-l $destination && @backups) {
|
||||
my $backup = pop(@backups);
|
||||
unless (rename($backup, $destination)) {
|
||||
my $error = $!;
|
||||
$lock->release;
|
||||
die "Cannot restore interrupted common repository $backup: $error\n";
|
||||
}
|
||||
}
|
||||
remove_tree($_) for grep { -d $_ && !-l $_ } @backups;
|
||||
|
||||
for my $staging (bsd_glob("$base/.common.*")) {
|
||||
next if $staging =~ m{/\.common\.previous\.};
|
||||
remove_tree($staging) if -d $staging && !-l $staging;
|
||||
}
|
||||
$lock->release;
|
||||
return 1;
|
||||
}
|
||||
|
||||
# resolve_mock_cfg($os_id, $rel, $arch[, $cfg_dir]): the mock config for EL release $rel on this
|
||||
# host, <id>+epel-<rel>-<arch>. /etc/os-release says 'almalinux' where mock-core-configs names the
|
||||
# file 'alma', so the short form is tried too. $cfg_dir defaults to /etc/mock.
|
||||
sub resolve_mock_cfg {
|
||||
my ($os_id, $rel, $arch, $cfg_dir) = @_;
|
||||
$cfg_dir //= '/etc/mock';
|
||||
my %short_forms = (
|
||||
almalinux => 'alma',
|
||||
'centos-stream' => 'centos-stream',
|
||||
rocky => 'rocky',
|
||||
);
|
||||
# Resolve by CONFIG-FILE existence, not by running `mock --print-root-path`: the latter can fail
|
||||
# transiently (bootstrap chroot setup, a concurrent mock holding a lock) and made el10 flakily
|
||||
# "resolve" to the long form that has no .cfg. Checking <cfg_dir>/<cfg>.cfg is deterministic.
|
||||
for my $id ($os_id, (exists $short_forms{$os_id} ? ($short_forms{$os_id}) : ())) {
|
||||
my $candidate = "${id}+epel-${rel}-${arch}";
|
||||
if (-f "$cfg_dir/${candidate}.cfg") {
|
||||
print "Mock config resolved: $candidate\n" if $id ne $os_id;
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
my $short = $short_forms{$os_id} // $os_id;
|
||||
die "Could not find mock config for ${os_id}+epel-${rel}-${arch} "
|
||||
. "(tried $cfg_dir/${os_id}+epel-${rel}-${arch}.cfg and $cfg_dir/${short}+epel-${rel}-${arch}.cfg)\n";
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
+16
-3
@@ -31,10 +31,11 @@
|
||||
# PER-ARCH SETS (review concern #3 -- the arch matrix must be valid):
|
||||
# * Compiled, arch-specific deps that genuinely build on BOTH arches are listed for amd64 AND
|
||||
# ppc64el: ipmitool-xcat, conserver-xcat, goconserver (debian/control Architecture: any / *-ppc64el).
|
||||
# * The noarch boot components (syslinux-xcat, grub2-xcat, elilo-xcat, xnba-undi) are
|
||||
# * The noarch boot components (syslinux-xcat, grub2-xcat, elilo-xcat, xnba-undi, ipxe-xcat) are
|
||||
# Architecture:all: their SOURCE is x86-only (syslinux compiles with nasm/gcc-multilib; elilo/xnba
|
||||
# are x86/EFI loaders; grub2-xcat is config/scripts), so they are BUILT ONCE on amd64 -- SINGLE
|
||||
# PRODUCER, concern #3b -- and, being arch:all, assembled into EVERY arch's Packages index. They
|
||||
# are x86/EFI loaders; grub2-xcat is config/scripts; ipxe-xcat repackages the iPXE release
|
||||
# tree), so they are BUILT ONCE on amd64 -- SINGLE PRODUCER, concern #3b -- and, being
|
||||
# arch:all, assembled into EVERY arch's Packages index. They
|
||||
# ARE listed for ppc64el too, as REQUIRED-PRESENT: a ppc MN needs them for netboot, so the gate
|
||||
# must verify the ppc repo carries them (matching the EL manifest and the historical 2.16 ppc dep
|
||||
# repo, minus the obsolete yaboot-xcat). sbuild-all.pl's build phase SKIPS an Architecture:all
|
||||
@@ -67,6 +68,7 @@ syslinux-xcat=3.86-2
|
||||
grub2-xcat=2.12-2
|
||||
elilo-xcat=3.14-6
|
||||
xnba-undi=1.21.1-1
|
||||
ipxe-xcat=2.0.0-1
|
||||
xcat-genesis-base=2.*
|
||||
|
||||
[focal-ppc64el]
|
||||
@@ -77,6 +79,7 @@ syslinux-xcat=3.86-2
|
||||
grub2-xcat=2.12-2
|
||||
elilo-xcat=3.14-6
|
||||
xnba-undi=1.21.1-1
|
||||
ipxe-xcat=2.0.0-1
|
||||
xcat-genesis-base=2.*
|
||||
|
||||
[focal-riscv64]
|
||||
@@ -87,6 +90,7 @@ syslinux-xcat=3.86-2
|
||||
grub2-xcat=2.12-2
|
||||
elilo-xcat=3.14-6
|
||||
xnba-undi=1.21.1-1
|
||||
ipxe-xcat=2.0.0-1
|
||||
|
||||
# ============================ jammy (ubuntu22.04) ============================
|
||||
[jammy-amd64]
|
||||
@@ -97,6 +101,7 @@ syslinux-xcat=3.86-2
|
||||
grub2-xcat=2.12-2
|
||||
elilo-xcat=3.14-6
|
||||
xnba-undi=1.21.1-1
|
||||
ipxe-xcat=2.0.0-1
|
||||
xcat-genesis-base=2.*
|
||||
|
||||
[jammy-ppc64el]
|
||||
@@ -107,6 +112,7 @@ syslinux-xcat=3.86-2
|
||||
grub2-xcat=2.12-2
|
||||
elilo-xcat=3.14-6
|
||||
xnba-undi=1.21.1-1
|
||||
ipxe-xcat=2.0.0-1
|
||||
xcat-genesis-base=2.*
|
||||
|
||||
[jammy-riscv64]
|
||||
@@ -117,6 +123,7 @@ syslinux-xcat=3.86-2
|
||||
grub2-xcat=2.12-2
|
||||
elilo-xcat=3.14-6
|
||||
xnba-undi=1.21.1-1
|
||||
ipxe-xcat=2.0.0-1
|
||||
|
||||
# ============================ noble (ubuntu24.04) ============================
|
||||
[noble-amd64]
|
||||
@@ -127,6 +134,7 @@ syslinux-xcat=3.86-2
|
||||
grub2-xcat=2.12-2
|
||||
elilo-xcat=3.14-6
|
||||
xnba-undi=1.21.1-1
|
||||
ipxe-xcat=2.0.0-1
|
||||
xcat-genesis-base=2.*
|
||||
|
||||
[noble-ppc64el]
|
||||
@@ -137,6 +145,7 @@ syslinux-xcat=3.86-2
|
||||
grub2-xcat=2.12-2
|
||||
elilo-xcat=3.14-6
|
||||
xnba-undi=1.21.1-1
|
||||
ipxe-xcat=2.0.0-1
|
||||
xcat-genesis-base=2.*
|
||||
|
||||
[noble-riscv64]
|
||||
@@ -147,6 +156,7 @@ syslinux-xcat=3.86-2
|
||||
grub2-xcat=2.12-2
|
||||
elilo-xcat=3.14-6
|
||||
xnba-undi=1.21.1-1
|
||||
ipxe-xcat=2.0.0-1
|
||||
|
||||
# ============================ resolute (ubuntu26.04) =========================
|
||||
[resolute-amd64]
|
||||
@@ -157,6 +167,7 @@ syslinux-xcat=3.86-2
|
||||
grub2-xcat=2.12-2
|
||||
elilo-xcat=3.14-6
|
||||
xnba-undi=1.21.1-1
|
||||
ipxe-xcat=2.0.0-1
|
||||
xcat-genesis-base=2.*
|
||||
|
||||
[resolute-ppc64el]
|
||||
@@ -167,6 +178,7 @@ syslinux-xcat=3.86-2
|
||||
grub2-xcat=2.12-2
|
||||
elilo-xcat=3.14-6
|
||||
xnba-undi=1.21.1-1
|
||||
ipxe-xcat=2.0.0-1
|
||||
xcat-genesis-base=2.*
|
||||
|
||||
[resolute-riscv64]
|
||||
@@ -177,6 +189,7 @@ syslinux-xcat=3.86-2
|
||||
grub2-xcat=2.12-2
|
||||
elilo-xcat=3.14-6
|
||||
xnba-undi=1.21.1-1
|
||||
ipxe-xcat=2.0.0-1
|
||||
|
||||
# [shared] is NOT a build target. It describes the ONE pool the OpenEmbedded Genesis release is
|
||||
# published into (pool/main/xcat-genesis-openembedded), which every suite indexes and which no
|
||||
|
||||
+23
-6
@@ -44,7 +44,14 @@ $build_timestamp = time() unless defined $build_timestamp;
|
||||
# The maintained debian/ is at ./debian in the copied package dir; the upstream source is cloned fresh
|
||||
# at the pinned SHA into ./gcsrc, the maintained debian/ copied in, and dpkg-buildpackage run there
|
||||
# (its .deb(s) land in the copied package dir, which the collector picks up).
|
||||
my $build = <<'BUILD';
|
||||
# The build script below is lifted by t/goconserver_cross_build.t and run with the commands it
|
||||
# calls shadowed. Keep the marker: the test dies when it can no longer find this region.
|
||||
my $host_deb_arch = `dpkg --print-architecture 2>/dev/null`;
|
||||
chomp $host_deb_arch;
|
||||
die "FATAL: cannot read the build host architecture from dpkg\n"
|
||||
unless $host_deb_arch =~ /^[a-z0-9]+$/;
|
||||
|
||||
my $build = "HOST_DEB_ARCH=$host_deb_arch\n" . <<'BUILD';
|
||||
set -e
|
||||
VERSION=0.3.3
|
||||
REPO=https://github.com/xcat2/goconserver.git
|
||||
@@ -52,13 +59,23 @@ REF=6166fe5ec1c5b3c20475e322a9f0e8e93c87e45f
|
||||
GO_PIN=1.25.12
|
||||
|
||||
# pinned modern Go toolchain (static CGO-free build, portable across codenames; reproducible compiler)
|
||||
go_arch=$(dpkg --print-architecture); [ "$go_arch" = ppc64el ] && go_arch=ppc64le
|
||||
echo "installing pinned go${GO_PIN} (${go_arch}) for the goconserver build"
|
||||
rm -rf /usr/local/go
|
||||
curl -fsSL "https://go.dev/dl/go${GO_PIN}.linux-${go_arch}.tar.gz" | tar -C /usr/local -xz
|
||||
export PATH=/usr/local/go/bin:$PATH
|
||||
# The toolchain is the BUILD HOST's and the target comes from GOARCH, because riscv64 has no build
|
||||
# host: its chroot runs under qemu-user, and a riscv64 `go build` there parks in futex_wait and never
|
||||
# returns. Go cross-compiles a CGO-free binary, and a Go toolchain is statically linked, so the host
|
||||
# one runs inside the foreign chroot at native speed. HOST_DEB_ARCH is stamped in by sbuild.pl: it
|
||||
# cannot be read here, because qemu makes the chroot's dpkg and uname both answer for the target.
|
||||
deb_to_goarch() { case "$1" in ppc64el) echo ppc64le;; *) echo "$1";; esac; }
|
||||
go_host_arch=$(deb_to_goarch "$HOST_DEB_ARCH")
|
||||
go_target_arch=$(deb_to_goarch "$(dpkg --print-architecture)")
|
||||
echo "installing pinned go${GO_PIN} (${go_host_arch}) to compile for ${go_target_arch}"
|
||||
gotoolchain="$PWD/.gotoolchain"
|
||||
mkdir -p "$gotoolchain"
|
||||
curl -fsSL "https://go.dev/dl/go${GO_PIN}.linux-${go_host_arch}.tar.gz" | tar -C "$gotoolchain" --strip-components=1 -xz
|
||||
export PATH="$gotoolchain/bin:$PATH"
|
||||
export GOTOOLCHAIN=local # use exactly the pinned toolchain; never auto-download another
|
||||
export GOOS=linux GOARCH="$go_target_arch"
|
||||
go version
|
||||
go env GOHOSTARCH GOARCH
|
||||
|
||||
if [ -n "${SOURCE_DATE_EPOCH:-}" ]; then
|
||||
SNAP_TS=$(date -d "@$SOURCE_DATE_EPOCH" --utc '+%Y%m%d%H%M')
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# The licence texts stay byte-identical to their upstream sources.
|
||||
licenses/** -whitespace
|
||||
@@ -0,0 +1,87 @@
|
||||
ipxe-xcat
|
||||
=========
|
||||
|
||||
This package installs the ipxeboot.tar.gz tree of the iPXE v2.0.0 release
|
||||
under /tftpboot/xcat/ipxe. Nothing is rebuilt, and only the two shims are
|
||||
replaced: see The shim. The x86_64-sb
|
||||
and arm64-sb builds carry their Secure Boot signatures inside the files, and
|
||||
the shim finds snponly.efi and ipxe.efi by name in its own directory, so the
|
||||
package keeps every name, symlink and byte of the release tree.
|
||||
|
||||
Files
|
||||
-----
|
||||
|
||||
ipxeboot-2.0.0.tar.gz
|
||||
The ipxeboot.tar.gz asset of
|
||||
https://github.com/ipxe/ipxe/releases/tag/v2.0.0, renamed. Its SHA-256,
|
||||
01a526d4cc791fc30362259c609d6c506cc64a7bdff51b9a5eb788354e17eee1, is the
|
||||
digest that GitHub publishes for the asset.
|
||||
|
||||
ipxe-2.0.0-source.tar.gz
|
||||
The source archive of tag v2.0.0, commit
|
||||
12798ec29aa8a64d8675c4378b99f5fe28447afb, from
|
||||
https://github.com/ipxe/ipxe/archive/refs/tags/v2.0.0.tar.gz. Its content
|
||||
equals "git archive --prefix=ipxe-2.0.0/ v2.0.0". snpnet.c and undinet.c
|
||||
are GPL2_OR_LATER without the UBDL, so snponly.efi and undionly.kpxe are
|
||||
GPLv2+ as a whole. The package installs this archive with the binaries.
|
||||
|
||||
ipxe-shimx64.efi, ipxe-shimaa64.efi
|
||||
The ipxe-shimx64.efi and ipxe-shimaa64.efi assets of
|
||||
https://github.com/ipxe/shim/releases/tag/ipxe-16.1, as ipxe replaced
|
||||
them on 2026-05-27. Their SHA-256 values are the digests that GitHub
|
||||
publishes for the assets.
|
||||
|
||||
SHA256SUMS
|
||||
The SHA-256 of both archives and both shims. Both builders check it
|
||||
before the build.
|
||||
|
||||
payload.sha256
|
||||
One line for each directory, file and symlink of the release tree, with
|
||||
the SHA-256 of each file and the target of each symlink. After the build,
|
||||
both builders unpack the RPM or deb and compare its /tftpboot/xcat/ipxe
|
||||
with this list, entry for entry, with verify-payload.pl. A difference
|
||||
fails the build.
|
||||
|
||||
licenses/
|
||||
ipxe/COPYING, COPYING.GPLv2 and COPYING.UBDL are from iPXE tag v2.0.0.
|
||||
shim/COPYRIGHT is from ipxe/shim tag ipxe-16.1.
|
||||
shim/openssl/LICENSE is from openssl/openssl tag OpenSSL_1_0_2k, the
|
||||
OpenSSL version that shim 16.1 carries in Cryptlib.
|
||||
shim/gnu-efi/README.efilib is from rhboot/gnu-efi commit dc7fd96, the
|
||||
gnu-efi commit that tag ipxe-16.1 pins.
|
||||
|
||||
The shim
|
||||
--------
|
||||
|
||||
The release tree carries shim 16.1 as x86_64-sb/shimx64.efi and
|
||||
arm64-sb/shimaa64.efi, signed by the Microsoft Corporation UEFI CA 2011
|
||||
only. Firmware that trusts only the UEFI CA 2023 refuses them with Secure
|
||||
Boot on. On 2026-05-27 ipxe replaced the ipxe/shim ipxe-16.1 release assets
|
||||
with a build signed by both CAs. Both builders install ipxe-shimx64.efi and
|
||||
ipxe-shimaa64.efi over the shims of the tree, under the same names, so the
|
||||
ipxe-shim.efi and snponly-shim.efi links still point to them. payload.sha256
|
||||
lists the digests of the replacements.
|
||||
|
||||
Update to a new release
|
||||
-----------------------
|
||||
|
||||
1. Download ipxeboot.tar.gz from the release, and compare its SHA-256 with
|
||||
the digest on the release page.
|
||||
2. Download the source archive of the tag, and compare its content with
|
||||
"git archive" of the tag.
|
||||
3. Replace both archives. Download ipxe-shimx64.efi and ipxe-shimaa64.efi
|
||||
from the latest ipxe/shim release, and compare their SHA-256 with the
|
||||
digests on its page. Drop them and their install lines when the shims of
|
||||
the new tree carry the UEFI CA 2023 signature.
|
||||
4. Write SHA256SUMS with sha256sum.
|
||||
5. Write payload.sha256 from the tree with the shims in place:
|
||||
|
||||
mkdir tree
|
||||
tar -xzf ipxeboot-<version>.tar.gz --strip-components=1 -C tree
|
||||
cp ipxe-shimx64.efi tree/x86_64-sb/shimx64.efi
|
||||
cp ipxe-shimaa64.efi tree/arm64-sb/shimaa64.efi
|
||||
./verify-payload.pl --generate tree > payload.sha256
|
||||
|
||||
6. Update licenses/ when the release changes its licence texts or its shim.
|
||||
7. Set the version in ipxe-xcat.spec and debian/changelog, and the ipxe-xcat
|
||||
pins in packages-manifest.conf and debs-manifest.conf.
|
||||
@@ -0,0 +1,4 @@
|
||||
9ed6d029be901a0ccc87cb2e5f9c774620f30f84ebdd507c6dd3e1e6229b7bd5 ipxe-2.0.0-source.tar.gz
|
||||
31c6d8ef9ed24dc810dd7b951cf86b2e9036a02dea8a32548c0e1c0ae4d6f1c9 ipxe-shimaa64.efi
|
||||
5eecca2780bd49c900565e124516a1bd666ec5e012825f34991b6ba1ef2fa6cf ipxe-shimx64.efi
|
||||
01a526d4cc791fc30362259c609d6c506cc64a7bdff51b9a5eb788354e17eee1 ipxeboot-2.0.0.tar.gz
|
||||
@@ -0,0 +1,6 @@
|
||||
ipxe-xcat (2.0.0-1) unstable; urgency=medium
|
||||
|
||||
* Package the ipxeboot.tar.gz tree of the iPXE v2.0.0 release, with the
|
||||
ipxe/shim 16.1 shims signed by both Microsoft UEFI CAs.
|
||||
|
||||
-- xCAT <xcat-user@lists.sourceforge.net> Fri, 25 Sep 2026 12:00:00 +0000
|
||||
@@ -0,0 +1 @@
|
||||
12
|
||||
@@ -0,0 +1,16 @@
|
||||
Source: ipxe-xcat
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Maintainer: xCAT <xcat-user@lists.sourceforge.net>
|
||||
Build-Depends: debhelper (>= 12)
|
||||
Standards-Version: 4.5.0
|
||||
Homepage: https://ipxe.org/
|
||||
|
||||
Package: ipxe-xcat
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}
|
||||
Description: iPXE network boot binaries from the upstream release
|
||||
The ipxeboot.tar.gz tree of the iPXE 2.0.0 release, installed unchanged
|
||||
under /tftpboot/xcat/ipxe. It carries the signed Secure Boot builds and
|
||||
their shim. The source archive of the release tag is installed with the
|
||||
documentation.
|
||||
@@ -0,0 +1,37 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: iPXE
|
||||
Source: https://github.com/ipxe/ipxe/releases/tag/v2.0.0
|
||||
Comment: ipxeboot-2.0.0.tar.gz is the ipxeboot.tar.gz asset of the iPXE v2.0.0
|
||||
release, unchanged. ipxe-2.0.0-source.tar.gz is the source archive of tag
|
||||
v2.0.0, installed as /usr/share/doc/ipxe-xcat/ipxe-2.0.0-source.tar.gz.
|
||||
.
|
||||
The files x86_64-sb/shimx64.efi and arm64-sb/shimaa64.efi in the release tree
|
||||
are shim 16.1, built by the iPXE project from ipxe/shim tag ipxe-16.1. Shim is
|
||||
under the BSD licence in licenses/shim/COPYRIGHT and includes OpenSSL 1.0.2k
|
||||
(licenses/shim/openssl/LICENSE) and gnu-efi (licenses/shim/gnu-efi/README.efilib).
|
||||
.
|
||||
The licence texts are installed in /usr/share/doc/ipxe-xcat/licenses/.
|
||||
|
||||
Files: *
|
||||
Copyright: Michael Brown <mbrown@fensystems.co.uk> and the iPXE contributors
|
||||
License: GPL-2+
|
||||
iPXE files are licensed under the GNU General Public License, version 2 or
|
||||
(at your option) any later version, unless the file states another licence.
|
||||
Some files are licensed under version 2 only, some under BSD or MIT terms,
|
||||
and most may also be used under the Unmodified Binary Distribution Licence
|
||||
(licenses/ipxe/COPYING.UBDL). Each file in the source archive states its
|
||||
own licence.
|
||||
.
|
||||
On Debian systems, the complete text of the GNU General Public License
|
||||
version 2 can be found in /usr/share/common-licenses/GPL-2.
|
||||
|
||||
Files: debian/*
|
||||
Copyright: xCAT contributors
|
||||
License: GPL-2+
|
||||
This packaging is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation; either version 2 of the License, or (at your option)
|
||||
any later version.
|
||||
.
|
||||
On Debian systems, the complete text of the GNU General Public License
|
||||
version 2 can be found in /usr/share/common-licenses/GPL-2.
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/make -f
|
||||
# The payload is the upstream release tree, byte for byte: nothing strips it, and dh_compress and
|
||||
# dh_fixperms leave /tftpboot and the licence texts alone.
|
||||
|
||||
VERSION := $(shell dpkg-parsechangelog -S Version | sed 's/-[^-]*$$//')
|
||||
DEST := debian/ipxe-xcat
|
||||
DOC := $(DEST)/usr/share/doc/ipxe-xcat
|
||||
|
||||
build build-arch build-indep:
|
||||
|
||||
clean:
|
||||
dh_testdir
|
||||
dh_clean
|
||||
|
||||
binary-arch:
|
||||
|
||||
binary-indep:
|
||||
dh_testdir
|
||||
dh_testroot
|
||||
dh_prep
|
||||
install -d $(DEST)/tftpboot/xcat/ipxe $(DOC)
|
||||
tar -xzf ipxeboot-$(VERSION).tar.gz --no-same-owner --strip-components=1 -C $(DEST)/tftpboot/xcat/ipxe
|
||||
install -m 0644 ipxe-shimx64.efi $(DEST)/tftpboot/xcat/ipxe/x86_64-sb/shimx64.efi
|
||||
install -m 0644 ipxe-shimaa64.efi $(DEST)/tftpboot/xcat/ipxe/arm64-sb/shimaa64.efi
|
||||
install -m 0644 ipxe-$(VERSION)-source.tar.gz $(DOC)/
|
||||
cp -R licenses $(DOC)/licenses
|
||||
dh_installdocs
|
||||
dh_installchangelogs
|
||||
dh_compress -Xlicenses/
|
||||
dh_fixperms -Xtftpboot/
|
||||
dh_installdeb
|
||||
dh_gencontrol
|
||||
dh_md5sums
|
||||
dh_builddeb
|
||||
|
||||
binary: binary-indep binary-arch
|
||||
|
||||
.PHONY: build build-arch build-indep clean binary-arch binary-indep binary
|
||||
@@ -0,0 +1 @@
|
||||
3.0 (native)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,59 @@
|
||||
# The payload is the upstream release, byte for byte: signed EFI files must not be stripped or
|
||||
# otherwise touched by the build-root policy scripts.
|
||||
%global debug_package %{nil}
|
||||
%global __os_install_post %{nil}
|
||||
|
||||
Name: ipxe-xcat
|
||||
Version: 2.0.0
|
||||
Release: 1
|
||||
Summary: iPXE network boot binaries from the upstream release
|
||||
License: GPL-2.0-only AND GPL-2.0-or-later AND BSD-2-Clause AND BSD-2-Clause-Patent AND BSD-3-Clause AND MIT AND OpenSSL
|
||||
URL: https://ipxe.org/
|
||||
BuildArch: noarch
|
||||
|
||||
Source0: ipxeboot-%{version}.tar.gz
|
||||
Source1: ipxe-%{version}-source.tar.gz
|
||||
Source2: licenses/ipxe/COPYING
|
||||
Source3: licenses/ipxe/COPYING.GPLv2
|
||||
Source4: licenses/ipxe/COPYING.UBDL
|
||||
Source5: licenses/shim/COPYRIGHT
|
||||
Source6: licenses/shim/openssl/LICENSE
|
||||
Source7: licenses/shim/gnu-efi/README.efilib
|
||||
Source8: ipxe-shimx64.efi
|
||||
Source9: ipxe-shimaa64.efi
|
||||
|
||||
%description
|
||||
The ipxeboot.tar.gz tree of the iPXE %{version} release, installed under
|
||||
/tftpboot/xcat/ipxe. It carries the signed Secure Boot builds and their
|
||||
shim. The shims are the ipxe/shim 16.1 assets signed by both Microsoft UEFI
|
||||
CAs, 2011 and 2023, and every other file is unchanged. The source archive
|
||||
of the release tag is installed with the documentation.
|
||||
|
||||
%prep
|
||||
%setup -q -c -T
|
||||
install -D -m 0644 %{SOURCE2} licenses/ipxe/COPYING
|
||||
install -D -m 0644 %{SOURCE3} licenses/ipxe/COPYING.GPLv2
|
||||
install -D -m 0644 %{SOURCE4} licenses/ipxe/COPYING.UBDL
|
||||
install -D -m 0644 %{SOURCE5} licenses/shim/COPYRIGHT
|
||||
install -D -m 0644 %{SOURCE6} licenses/shim/openssl/LICENSE
|
||||
install -D -m 0644 %{SOURCE7} licenses/shim/gnu-efi/README.efilib
|
||||
|
||||
%build
|
||||
|
||||
%install
|
||||
mkdir -p %{buildroot}/tftpboot/xcat/ipxe
|
||||
tar -xzf %{SOURCE0} --no-same-owner --strip-components=1 -C %{buildroot}/tftpboot/xcat/ipxe
|
||||
install -m 0644 %{SOURCE8} %{buildroot}/tftpboot/xcat/ipxe/x86_64-sb/shimx64.efi
|
||||
install -m 0644 %{SOURCE9} %{buildroot}/tftpboot/xcat/ipxe/arm64-sb/shimaa64.efi
|
||||
install -D -m 0644 %{SOURCE1} %{buildroot}%{_pkgdocdir}/ipxe-%{version}-source.tar.gz
|
||||
|
||||
%files
|
||||
/tftpboot/xcat/ipxe
|
||||
%license licenses/ipxe licenses/shim
|
||||
%dir %{_pkgdocdir}
|
||||
%doc %{_pkgdocdir}/ipxe-%{version}-source.tar.gz
|
||||
|
||||
%changelog
|
||||
* Fri Sep 25 2026 xCAT <xcat-user@lists.sourceforge.net> - 2.0.0-1
|
||||
- Package the ipxeboot.tar.gz tree of the iPXE v2.0.0 release, with the
|
||||
ipxe/shim 16.1 shims signed by both Microsoft UEFI CAs
|
||||
Binary file not shown.
@@ -0,0 +1,12 @@
|
||||
In general iPXE files are licensed under the GPL. For historical
|
||||
reasons, individual files may contain their own licence declarations.
|
||||
Most builds of iPXE do not contain all iPXE code (in particular, most
|
||||
builds will include only one driver), and so the overall licence can
|
||||
vary depending on what target you are building.
|
||||
|
||||
The resultant applicable licence(s) for any particular build can be
|
||||
determined by using "make bin/xxxxxxx.yyy.licence"; for example:
|
||||
|
||||
make bin/rtl8139.rom.licence
|
||||
|
||||
to determine the resultant licence(s) for the build bin/rtl8139.rom
|
||||
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
@@ -0,0 +1,59 @@
|
||||
UNMODIFIED BINARY DISTRIBUTION LICENCE
|
||||
|
||||
|
||||
PREAMBLE
|
||||
|
||||
The GNU General Public License provides a legal guarantee that
|
||||
software covered by it remains free (in the sense of freedom, not
|
||||
price). It achieves this guarantee by imposing obligations on anyone
|
||||
who chooses to distribute the software.
|
||||
|
||||
Some of these obligations may be seen as unnecessarily burdensome. In
|
||||
particular, when the source code for the software is already publicly
|
||||
and freely available, there is minimal value in imposing upon each
|
||||
distributor the obligation to provide the complete source code (or an
|
||||
equivalent written offer to provide the complete source code).
|
||||
|
||||
This Licence allows for the distribution of unmodified binaries built
|
||||
from publicly available source code, without imposing the obligations
|
||||
of the GNU General Public License upon anyone who chooses to
|
||||
distribute only the unmodified binaries built from that source code.
|
||||
|
||||
The extra permissions granted by this Licence apply only to unmodified
|
||||
binaries built from source code which has already been made available
|
||||
to the public in accordance with the terms of the GNU General Public
|
||||
Licence. Nothing in this Licence allows for the creation of
|
||||
closed-source modified versions of the Program. Any modified versions
|
||||
of the Program are subject to the usual terms and conditions of the
|
||||
GNU General Public License.
|
||||
|
||||
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
This Licence applies to any Program or other work which contains a
|
||||
notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this Unmodified Binary Distribution Licence. All
|
||||
terms used in the text of this Licence are to be interpreted as they
|
||||
are used in version 2 of the GNU General Public License as published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If you have made this Program available to the public in both source
|
||||
code and executable form in accordance with the terms of the GNU
|
||||
General Public License as published by the Free Software Foundation;
|
||||
either version 2 of the License, or (at your option) any later
|
||||
version, then you are hereby granted an additional permission to use,
|
||||
copy, and distribute the unmodified executable form of this Program
|
||||
(the "Unmodified Binary") without restriction, including the right to
|
||||
permit persons to whom the Unmodified Binary is furnished to do
|
||||
likewise, subject to the following conditions:
|
||||
|
||||
- when started running, the Program must display an announcement which
|
||||
includes the details of your existing publication of the Program
|
||||
made in accordance with the terms of the GNU General Public License.
|
||||
For example, the Program could display the URL of the publicly
|
||||
available source code from which the Unmodified Binary was built.
|
||||
|
||||
- when exercising your right to grant permissions under this Licence,
|
||||
you do not need to refer directly to the text of this Licence, but
|
||||
you may not grant permissions beyond those granted to you by this
|
||||
Licence.
|
||||
@@ -0,0 +1,30 @@
|
||||
Copyright 2012 Red Hat, Inc <mjg@redhat.com>
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Significant portions of this code are derived from Tianocore
|
||||
(http://tianocore.sf.net) and are Copyright 2009-2012 Intel
|
||||
Corporation.
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
The files in the "lib" and "inc" subdirectories are using the EFI Application
|
||||
Toolkit distributed by Intel at http://developer.intel.com/technology/efi
|
||||
|
||||
This code is covered by the following agreement:
|
||||
|
||||
Copyright (c) 1998-2000 Intel Corporation
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this list of conditions and
|
||||
the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice, this list of conditions
|
||||
and the following disclaimer in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE. THE EFI SPECIFICATION AND ALL OTHER INFORMATION
|
||||
ON THIS WEB SITE ARE PROVIDED "AS IS" WITH NO WARRANTIES, AND ARE SUBJECT
|
||||
TO CHANGE WITHOUT NOTICE.
|
||||
@@ -0,0 +1,127 @@
|
||||
|
||||
LICENSE ISSUES
|
||||
==============
|
||||
|
||||
The OpenSSL toolkit stays under a dual license, i.e. both the conditions of
|
||||
the OpenSSL License and the original SSLeay license apply to the toolkit.
|
||||
See below for the actual license texts. Actually both licenses are BSD-style
|
||||
Open Source licenses. In case of any license issues related to OpenSSL
|
||||
please contact openssl-core@openssl.org.
|
||||
|
||||
OpenSSL License
|
||||
---------------
|
||||
|
||||
/* ====================================================================
|
||||
* Copyright (c) 1998-2016 The OpenSSL Project. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. All advertising materials mentioning features or use of this
|
||||
* software must display the following acknowledgment:
|
||||
* "This product includes software developed by the OpenSSL Project
|
||||
* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
|
||||
*
|
||||
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
|
||||
* endorse or promote products derived from this software without
|
||||
* prior written permission. For written permission, please contact
|
||||
* openssl-core@openssl.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "OpenSSL"
|
||||
* nor may "OpenSSL" appear in their names without prior written
|
||||
* permission of the OpenSSL Project.
|
||||
*
|
||||
* 6. Redistributions of any form whatsoever must retain the following
|
||||
* acknowledgment:
|
||||
* "This product includes software developed by the OpenSSL Project
|
||||
* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
|
||||
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
|
||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* This product includes cryptographic software written by Eric Young
|
||||
* (eay@cryptsoft.com). This product includes software written by Tim
|
||||
* Hudson (tjh@cryptsoft.com).
|
||||
*
|
||||
*/
|
||||
|
||||
Original SSLeay License
|
||||
-----------------------
|
||||
|
||||
/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
|
||||
* All rights reserved.
|
||||
*
|
||||
* This package is an SSL implementation written
|
||||
* by Eric Young (eay@cryptsoft.com).
|
||||
* The implementation was written so as to conform with Netscapes SSL.
|
||||
*
|
||||
* This library is free for commercial and non-commercial use as long as
|
||||
* the following conditions are aheared to. The following conditions
|
||||
* apply to all code found in this distribution, be it the RC4, RSA,
|
||||
* lhash, DES, etc., code; not just the SSL code. The SSL documentation
|
||||
* included with this distribution is covered by the same copyright terms
|
||||
* except that the holder is Tim Hudson (tjh@cryptsoft.com).
|
||||
*
|
||||
* Copyright remains Eric Young's, and as such any Copyright notices in
|
||||
* the code are not to be removed.
|
||||
* If this package is used in a product, Eric Young should be given attribution
|
||||
* as the author of the parts of the library used.
|
||||
* This can be in the form of a textual message at program startup or
|
||||
* in documentation (online or textual) provided with the package.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. All advertising materials mentioning features or use of this software
|
||||
* must display the following acknowledgement:
|
||||
* "This product includes cryptographic software written by
|
||||
* Eric Young (eay@cryptsoft.com)"
|
||||
* The word 'cryptographic' can be left out if the rouines from the library
|
||||
* being used are not cryptographic related :-).
|
||||
* 4. If you include any Windows specific code (or a derivative thereof) from
|
||||
* the apps directory (application code) you must include an acknowledgement:
|
||||
* "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* The licence and distribution terms for any publically available version or
|
||||
* derivative of this code cannot be changed. i.e. this code cannot simply be
|
||||
* copied and put under another distribution licence
|
||||
* [including the GNU Public Licence.]
|
||||
*/
|
||||
|
||||
Executable
+172
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/perl
|
||||
# ipxe-xcat/mockbuild.pl -- build the ipxe-xcat noarch RPM with mock from the committed release
|
||||
# archives. The archives are checked against SHA256SUMS before the build, and the built RPM payload
|
||||
# against payload.sha256 before anything is copied to --result-dir.
|
||||
use strict;
|
||||
use warnings;
|
||||
use Cwd qw(abs_path);
|
||||
use File::Basename qw(basename);
|
||||
use File::Copy qw(copy);
|
||||
use File::Path qw(make_path remove_tree);
|
||||
use FindBin qw($RealBin);
|
||||
use Getopt::Long qw(GetOptions);
|
||||
use lib "$RealBin/..", "$RealBin/../lib";
|
||||
use MockBuildUtils qw(resolve_mock_cfg);
|
||||
use XCAT::BuildUtils qw(capture_command digest_file print_step require_command run_command shell_quote);
|
||||
|
||||
my $pkg_dir = abs_path($RealBin);
|
||||
my $repo_root = abs_path("$pkg_dir/..");
|
||||
my $spec_file = "$pkg_dir/ipxe-xcat.spec";
|
||||
|
||||
my $work_dir = '/tmp/ipxe-xcat-mockbuild';
|
||||
my $mock_cfg = '';
|
||||
my $mock_uniqueext = '';
|
||||
my $result_dir = "$repo_root/build-output/list3/ipxe-xcat";
|
||||
my $log_dir = "$repo_root/build-logs/list3/ipxe-xcat";
|
||||
my $build_timestamp;
|
||||
|
||||
GetOptions(
|
||||
'work-dir=s' => \$work_dir,
|
||||
'mock-cfg=s' => \$mock_cfg,
|
||||
'mock-uniqueext=s' => \$mock_uniqueext,
|
||||
'result-dir=s' => \$result_dir,
|
||||
'log-dir=s' => \$log_dir,
|
||||
'build-timestamp=i' => \$build_timestamp,
|
||||
) or die usage();
|
||||
|
||||
die "Run as root (current uid=$>)\n" if $> != 0;
|
||||
require_command($_) for qw(mock rpm rpm2cpio cpio bash sha256sum);
|
||||
|
||||
my ($version, @sources) = spec_sources($spec_file);
|
||||
die "Could not parse Version from $spec_file\n" if !$version;
|
||||
|
||||
if (!$mock_cfg) {
|
||||
my $os_id = capture_command('bash', '-c', 'source /etc/os-release; echo $ID');
|
||||
my $arch = capture_command('uname', '-m');
|
||||
$mock_cfg = resolve_mock_cfg($os_id, 10, $arch);
|
||||
}
|
||||
my @uniqueext = $mock_uniqueext ne '' ? ('--uniqueext', $mock_uniqueext) : ();
|
||||
|
||||
my $epoch = $build_timestamp;
|
||||
if (!defined $epoch) {
|
||||
$epoch = `git -C \Q$repo_root\E log -1 --format=%ct HEAD 2>/dev/null` // '';
|
||||
chomp $epoch;
|
||||
$epoch = time() if $epoch !~ /^\d+$/;
|
||||
}
|
||||
$ENV{SOURCE_DATE_EPOCH} = $epoch;
|
||||
|
||||
print_step('Configuration');
|
||||
print "pkg_dir: $pkg_dir\n";
|
||||
print "version: $version\n";
|
||||
print "work_dir: $work_dir\n";
|
||||
print "result_dir: $result_dir\n";
|
||||
print "log_dir: $log_dir\n";
|
||||
print "mock_cfg: $mock_cfg\n";
|
||||
|
||||
print_step('Check the release archives');
|
||||
run_command('bash', '-c', 'cd ' . shell_quote($pkg_dir) . ' && sha256sum --check --strict SHA256SUMS');
|
||||
|
||||
print_step('Stage the sources');
|
||||
remove_tree($work_dir) if -d $work_dir;
|
||||
my $sources_dir = "$work_dir/sources";
|
||||
make_path($sources_dir, $result_dir, $log_dir);
|
||||
my %staged;
|
||||
for my $source (@sources) {
|
||||
my $name = basename($source);
|
||||
die "Two Source files share the name $name\n" if $staged{$name}++;
|
||||
copy("$pkg_dir/$source", "$sources_dir/$name")
|
||||
or die "Failed to copy $pkg_dir/$source: $!\n";
|
||||
}
|
||||
run_command('bash', '-c', 'cd ' . shell_quote($sources_dir)
|
||||
. ' && sha256sum --check --strict ' . shell_quote("$pkg_dir/SHA256SUMS"));
|
||||
|
||||
my $det_cfg = "$work_dir/mock-deterministic.cfg";
|
||||
open(my $cfg_fh, '>', $det_cfg) or die "Cannot write $det_cfg: $!\n";
|
||||
print {$cfg_fh} "include('/etc/mock/${mock_cfg}.cfg')\n";
|
||||
print {$cfg_fh} "config_opts['environment']['SOURCE_DATE_EPOCH'] = '$epoch'\n";
|
||||
close($cfg_fh) or die "Cannot write $det_cfg: $!\n";
|
||||
my @defines = map { ('--define', $_) }
|
||||
('use_source_date_epoch_as_buildtime 1', 'clamp_mtime_to_source_date_epoch 1', '_buildhost xcat-build');
|
||||
|
||||
print_step('Build the SRPM with mock');
|
||||
my $srpm_out = "$work_dir/srpm";
|
||||
make_path($srpm_out);
|
||||
run_mock('mock', '-r', $det_cfg, @uniqueext, '--buildsrpm', '--spec', $spec_file,
|
||||
'--sources', $sources_dir, '--resultdir', $srpm_out, @defines);
|
||||
my @srpms = glob("$srpm_out/ipxe-xcat-*.src.rpm");
|
||||
die "Expected one SRPM in $srpm_out, found " . scalar(@srpms) . "\n" if @srpms != 1;
|
||||
|
||||
print_step('Rebuild the RPM with mock');
|
||||
my $rpm_out = "$work_dir/rpm";
|
||||
make_path($rpm_out);
|
||||
run_mock('mock', '-r', $det_cfg, @uniqueext, '--rebuild', $srpms[0], '--resultdir', $rpm_out, @defines);
|
||||
my @rpms = glob("$rpm_out/ipxe-xcat-$version-*.noarch.rpm");
|
||||
die "Expected one ipxe-xcat noarch RPM in $rpm_out, found " . scalar(@rpms) . "\n" if @rpms != 1;
|
||||
my $rpm = $rpms[0];
|
||||
|
||||
print_step('Check the RPM payload');
|
||||
my $payload = "$work_dir/payload";
|
||||
make_path($payload);
|
||||
run_command('bash', '-o', 'pipefail', '-c', 'cd ' . shell_quote($payload)
|
||||
. ' && rpm2cpio ' . shell_quote($rpm) . ' | cpio -idm --quiet');
|
||||
run_command('perl', "$pkg_dir/verify-payload.pl", "$payload/tftpboot/xcat/ipxe",
|
||||
"$pkg_dir/payload.sha256");
|
||||
check_installed("$payload/usr/share/doc/ipxe-xcat/ipxe-$version-source.tar.gz",
|
||||
"$pkg_dir/ipxe-$version-source.tar.gz");
|
||||
for my $licence (grep { m{^licenses/} } @sources) {
|
||||
(my $installed = $licence) =~ s{^licenses/}{};
|
||||
check_installed("$payload/usr/share/licenses/ipxe-xcat/$installed", "$pkg_dir/$licence");
|
||||
}
|
||||
|
||||
print_step('Collect the results');
|
||||
for my $file ($rpm, $srpms[0]) {
|
||||
copy($file, $result_dir) or die "Failed to copy $file to $result_dir: $!\n";
|
||||
print "Copied: $result_dir/" . basename($file) . "\n";
|
||||
}
|
||||
for my $log (qw(build.log root.log state.log)) {
|
||||
copy("$rpm_out/$log", "$log_dir/$log") if -f "$rpm_out/$log";
|
||||
copy("$srpm_out/$log", "$log_dir/srpm-$log") if -f "$srpm_out/$log";
|
||||
}
|
||||
print_step('Completed');
|
||||
exit 0;
|
||||
|
||||
sub usage {
|
||||
return <<"USAGE";
|
||||
Usage: $0 [options]
|
||||
--work-dir PATH Temporary work directory (default: $work_dir)
|
||||
--mock-cfg NAME Mock config (default: the EL10 config of this host)
|
||||
--mock-uniqueext TEXT mock --uniqueext suffix for concurrent builds
|
||||
--result-dir PATH Output directory for the RPM and SRPM
|
||||
--log-dir PATH Output directory for the mock logs
|
||||
--build-timestamp EPOCH SOURCE_DATE_EPOCH for a reproducible build
|
||||
USAGE
|
||||
}
|
||||
|
||||
sub spec_sources {
|
||||
my ($path) = @_;
|
||||
open(my $fh, '<', $path) or die "Cannot read $path: $!\n";
|
||||
my ($version, @sources) = ('');
|
||||
while (my $line = <$fh>) {
|
||||
$version = $1 if $line =~ /^Version:\s*(\S+)/;
|
||||
push @sources, $1 if $line =~ /^Source\d*:\s*(\S+)/;
|
||||
}
|
||||
close($fh);
|
||||
s/%\{version\}/$version/g for @sources;
|
||||
return ($version, @sources);
|
||||
}
|
||||
|
||||
sub check_installed {
|
||||
my ($installed, $committed) = @_;
|
||||
die "Missing from the RPM payload: $installed\n" if !-f $installed || -l $installed;
|
||||
die "The RPM payload changed $installed\n" if digest_file($installed) ne digest_file($committed);
|
||||
}
|
||||
|
||||
# mock exits 30 when its package manager failed, most often a transient mirror error: retry once.
|
||||
sub run_mock {
|
||||
my (@command) = @_;
|
||||
my $ok = eval { run_command(@command) };
|
||||
return 1 if $ok;
|
||||
die $@ if $@ !~ /\(rc=30\)/;
|
||||
print "mock failed with rc=30 (package manager); retrying once\n";
|
||||
return run_command(@command);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
# ipxe-xcat payload manifest, written by verify-payload.pl --generate
|
||||
dir - arm32
|
||||
file 2a8d50db2b3b5f02302302b3161931c1142301f42d52c64c0005974b602b7092 arm32/ipxe-legacy.efi
|
||||
file fa58061710e6fab23fc27c6d200d510453c3f14f3bc0715fa6804111f1e5fc74 arm32/ipxe.efi
|
||||
file 206310f30306569ed2c6e0ef015b3ad24230b303a1ad1171e19c117c914a65d6 arm32/snponly.efi
|
||||
dir - arm64
|
||||
dir - arm64-sb
|
||||
link shimaa64.efi arm64-sb/ipxe-shim.efi
|
||||
file 01b2438e6dc354c343590c308f1d4f000bc098be4d2237540432023abdbddcb8 arm64-sb/ipxe.efi
|
||||
file 31c6d8ef9ed24dc810dd7b951cf86b2e9036a02dea8a32548c0e1c0ae4d6f1c9 arm64-sb/shimaa64.efi
|
||||
link shimaa64.efi arm64-sb/snponly-shim.efi
|
||||
file bed8da8639b45eeec55d41627349863efa5085a2113bb1193838b8a463e5f49e arm64-sb/snponly.efi
|
||||
file cff2abf51b7b491d7c5e2ef68c45003a0b4c5e866cb5b20f162569746d53390d arm64/ipxe-legacy.efi
|
||||
file a9cb6df506a68f3afa4bb94cf6cb8e3862a5a2ff3bf1d8027fea83f7e1d49217 arm64/ipxe.efi
|
||||
file 03665b4184e4a0b8b9a889de0c7fb833fc48b02385e3fa40a4460db1122f1b36 arm64/snponly.efi
|
||||
dir - i386
|
||||
file 9dc4aee199f582fb8f55cf8bfb4622998c5e71c2dde149d5bbdfe33121c925d8 i386/ipxe-legacy.efi
|
||||
file df9bab29487d20af3fe9dda46afc775009ba1c089d2216098b9a6e82d906939b i386/ipxe-legacy.pxe
|
||||
file ea63c6d3745e751e8db4f9c8d4c97efa883fb6d0fc57c697b3d13ad98c1bfe0c i386/ipxe.efi
|
||||
file a0f144a5f7e10e567d189606c6c2320c1f43a228f9fb6148e7e52f5009fd789f i386/ipxe.pxe
|
||||
file 81a086995758b889fba7f13d7c5255dc7eb87ed38350b8c03112b9f56556de98 i386/snponly.efi
|
||||
file ef6ff013f988c52feb3e543dccf0bdc50cd1d68b8a4ab84b8975cef33f64e848 i386/undionly.kpxe
|
||||
link x86_64/ipxe-legacy.efi ipxe-legacy.efi
|
||||
link x86_64/ipxe-legacy.pxe ipxe-legacy.pxe
|
||||
link x86_64/ipxe.efi ipxe.efi
|
||||
link x86_64/ipxe.pxe ipxe.pxe
|
||||
dir - loong64
|
||||
file 03b078b2ba00e97427b5d99b14035bdb7abdb48631aba8759cf95c24a2620b7e loong64/ipxe-legacy.efi
|
||||
file c326c8bf54d8fc40fa6730907ee07cb44e8151ecc1397e1ddd2c0edef7cb2872 loong64/ipxe.efi
|
||||
file fd7cb2df195ad5cc3a09f41fab23713afa23624ff664f83de0e7ca0fd5776fb7 loong64/snponly.efi
|
||||
dir - riscv32
|
||||
file 41f76da3efc3293887a701f35cac389c04a3a8110743673e37451e3c6e24e507 riscv32/ipxe-legacy.efi
|
||||
file 4afd51329213e50a4aaf24b397e8aba67910ca1e94ea3a6fab0e089d91ac69b8 riscv32/ipxe.efi
|
||||
file 0a901453bde1392051e53bb1e75584a4eb7cc9c75a27a2b34d7b26ad21734f8f riscv32/snponly.efi
|
||||
dir - riscv64
|
||||
file a72923e2ff600bd94628ef5f97a7ddd5c23fabdc9736fdaa2c22cfb7f0fe908d riscv64/ipxe-legacy.efi
|
||||
file 53c926c45043c1f2ad03aa70aa22921accc3202865c791cc6023fa17d92f8f24 riscv64/ipxe.efi
|
||||
file 95b9f6466e0d88fafe642c46d3d6024e4107cac24d2197664b62610f21a48585 riscv64/snponly.efi
|
||||
link x86_64-sb sb
|
||||
link x86_64/snponly.efi snponly.efi
|
||||
link x86_64/undionly.kpxe undionly.kpxe
|
||||
dir - x86_64
|
||||
dir - x86_64-sb
|
||||
link shimx64.efi x86_64-sb/ipxe-shim.efi
|
||||
file 6558e37887516b246d6a97122e8d18bedfe4197b7ba7f67bf1bf102a16678d33 x86_64-sb/ipxe.efi
|
||||
file 5eecca2780bd49c900565e124516a1bd666ec5e012825f34991b6ba1ef2fa6cf x86_64-sb/shimx64.efi
|
||||
link shimx64.efi x86_64-sb/snponly-shim.efi
|
||||
file b1e67c3e4a1e8708ddfd0079ad4505e3a02245acb55ee9a95437ab3c507be82a x86_64-sb/snponly.efi
|
||||
file 1d567f2e89a3c0cf0a184549918a3e799a47dc542df385c56e6b464e4765fae2 x86_64/ipxe-legacy.efi
|
||||
file fc37ef1e4a4bd5d22f8db3c84e1718ac8b03fe20156cf2fc4b9e2ae5e4a348db x86_64/ipxe-legacy.pxe
|
||||
file 868aa34057ff416ebf2fdfb5781de035e2c540477c04039198a9f8a9c6130034 x86_64/ipxe.efi
|
||||
file 0fb21c695c82fd70d4f89732ddcd285063776e2bbd71de9a2c39e9cfe4b180b8 x86_64/ipxe.pxe
|
||||
file f61c2ce34e05d7d857633df2e512d547df75b6aa18b2da152a7c9af222cfe28f x86_64/snponly.efi
|
||||
file 4186562d21ff54e970d905751c9f36d628e73a51a94afe4a6a42f925b0df448c x86_64/undionly.kpxe
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env perl
|
||||
# ipxe-xcat/sbuild.pl -- per-package Ubuntu/Debian builder for ipxe-xcat, the apt analogue of
|
||||
# ipxe-xcat/mockbuild.pl. Invoked by sbuild-all.pl per (codename,arch); also runnable standalone.
|
||||
# The build runs on a copy of the package tree inside the <codename>-<arch>-sbuild chroot, and it
|
||||
# checks the archives before dpkg-buildpackage and the built payload after it, so a deb that
|
||||
# differs from the release never reaches --result-dir.
|
||||
use strict;
|
||||
use warnings;
|
||||
use Cwd qw(abs_path);
|
||||
use File::Basename qw(basename);
|
||||
use Getopt::Long qw(GetOptions);
|
||||
use FindBin qw($RealBin);
|
||||
use lib "$RealBin/..";
|
||||
use BuildUtils qw(chroot_name build_deb_in_chroot);
|
||||
|
||||
my $pkg_dir = abs_path($RealBin);
|
||||
my $pkg = basename($pkg_dir);
|
||||
my ($codename, $arch, $chroot, $result_dir, $log_dir) = ('', '', '', '', '');
|
||||
my ($build_timestamp, $build_number, $skip_install) = (undef, undef, 0);
|
||||
# --log-dir, --build-number and --skip-install keep the command line sbuild-all.pl passes to every
|
||||
# builder; this package has no use for them.
|
||||
GetOptions(
|
||||
'codename=s' => \$codename, 'arch=s' => \$arch, 'chroot=s' => \$chroot,
|
||||
'result-dir=s' => \$result_dir, 'log-dir=s' => \$log_dir,
|
||||
'build-timestamp=i' => \$build_timestamp, 'build-number=i' => \$build_number,
|
||||
'skip-install!' => \$skip_install,
|
||||
) or die "bad options\n";
|
||||
$arch ||= `dpkg --print-architecture 2>/dev/null`; chomp $arch; $arch ||= 'amd64';
|
||||
die "FATAL: --codename required\n" unless $codename;
|
||||
$chroot ||= chroot_name($codename, $arch);
|
||||
$result_dir ||= "$pkg_dir/../build-output/sbuild/$codename/$arch";
|
||||
$build_timestamp = time() unless defined $build_timestamp;
|
||||
|
||||
# ipxe-xcat is Architecture:all and is built once on amd64 (see debs-manifest.conf).
|
||||
my $build = <<'BUILD';
|
||||
set -e
|
||||
sha256sum --check --strict SHA256SUMS
|
||||
dpkg-buildpackage -uc -us -b
|
||||
version=$(dpkg-parsechangelog -S Version)
|
||||
payload=$(mktemp -d)
|
||||
dpkg-deb -x "../ipxe-xcat_${version}_all.deb" "$payload"
|
||||
perl ./verify-payload.pl "$payload/tftpboot/xcat/ipxe" payload.sha256
|
||||
source_archive="ipxe-${version%-*}-source.tar.gz"
|
||||
grep -F " $source_archive" SHA256SUMS \
|
||||
| (cd "$payload/usr/share/doc/ipxe-xcat" && sha256sum --check --strict -)
|
||||
for licence in $(cd licenses && find . -type f); do
|
||||
cmp "licenses/$licence" "$payload/usr/share/doc/ipxe-xcat/licenses/$licence"
|
||||
done
|
||||
rm -rf "$payload"
|
||||
BUILD
|
||||
|
||||
build_deb_in_chroot(
|
||||
pkg => $pkg, chroot => $chroot, pkg_dir => $pkg_dir, result_dir => $result_dir,
|
||||
build_timestamp => $build_timestamp, build => $build,
|
||||
);
|
||||
Executable
+118
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/perl
|
||||
# verify-payload.pl -- compare an unpacked ipxe-xcat tree with payload.sha256.
|
||||
#
|
||||
# verify-payload.pl --generate <tree> > payload.sha256
|
||||
# verify-payload.pl <tree> <payload.sha256>
|
||||
#
|
||||
# Each manifest line is "<type>\t<value>\t<path>", sorted by path: "file" with the SHA-256 of the
|
||||
# content, "link" with the symlink target, "dir" with "-". Paths are relative to <tree>, and
|
||||
# symlinks are never followed. The check exits 0 when the tree matches the manifest entry for
|
||||
# entry, 1 when it differs, and 2 on a usage or read error.
|
||||
use strict;
|
||||
use warnings;
|
||||
use Digest::SHA ();
|
||||
use File::Find ();
|
||||
use Getopt::Long qw(GetOptions);
|
||||
|
||||
my $generate = 0;
|
||||
GetOptions('generate' => \$generate) or usage();
|
||||
|
||||
if ($generate) {
|
||||
usage() if @ARGV != 1;
|
||||
my $tree = scan_tree($ARGV[0]);
|
||||
print "# ipxe-xcat payload manifest, written by verify-payload.pl --generate\n";
|
||||
for my $path (sort keys %{$tree}) {
|
||||
print join("\t", @{ $tree->{$path} }, $path), "\n";
|
||||
}
|
||||
exit 0;
|
||||
}
|
||||
|
||||
usage() if @ARGV != 2;
|
||||
my ($root, $manifest_file) = @ARGV;
|
||||
my $expected = read_manifest($manifest_file);
|
||||
my $found = scan_tree($root);
|
||||
|
||||
my @problems;
|
||||
for my $path (sort keys %{$expected}) {
|
||||
my ($type, $value) = @{ $expected->{$path} };
|
||||
if (!exists $found->{$path}) {
|
||||
push @problems, "missing: $path";
|
||||
next;
|
||||
}
|
||||
my ($found_type, $found_value) = @{ $found->{$path} };
|
||||
if ($found_type ne $type) {
|
||||
push @problems, "type changed: $path (expected $type, found $found_type)";
|
||||
} elsif ($type eq 'file' && $found_value ne $value) {
|
||||
push @problems, "content changed: $path";
|
||||
} elsif ($type eq 'link' && $found_value ne $value) {
|
||||
push @problems, "link target changed: $path (expected $value, found $found_value)";
|
||||
}
|
||||
}
|
||||
push @problems, map { "unexpected: $_" } grep { !exists $expected->{$_} } sort keys %{$found};
|
||||
|
||||
if (@problems) {
|
||||
print STDERR "$_\n" for @problems;
|
||||
print STDERR "payload does not match $manifest_file: " . scalar(@problems) . " difference(s)\n";
|
||||
exit 1;
|
||||
}
|
||||
print "payload matches $manifest_file: " . scalar(keys %{$expected}) . " entries\n";
|
||||
exit 0;
|
||||
|
||||
sub usage {
|
||||
print STDERR "Usage: $0 --generate <tree>\n $0 <tree> <payload.sha256>\n";
|
||||
exit 2;
|
||||
}
|
||||
|
||||
sub fail {
|
||||
my ($message) = @_;
|
||||
print STDERR "$0: $message\n";
|
||||
exit 2;
|
||||
}
|
||||
|
||||
sub scan_tree {
|
||||
my ($dir) = @_;
|
||||
$dir =~ s{/+\z}{} if $dir ne '/';
|
||||
fail("not a directory: $dir") if -l $dir || !-d $dir;
|
||||
my %entries;
|
||||
File::Find::find({
|
||||
no_chdir => 1,
|
||||
wanted => sub {
|
||||
my $path = $File::Find::name;
|
||||
return if $path eq $dir;
|
||||
my $relative = substr($path, length($dir) + 1);
|
||||
lstat($path) or fail("cannot stat $path: $!");
|
||||
if (-l _) {
|
||||
my $target = readlink($path);
|
||||
fail("cannot read link $path: $!") if !defined $target;
|
||||
$entries{$relative} = ['link', $target];
|
||||
} elsif (-d _) {
|
||||
$entries{$relative} = ['dir', '-'];
|
||||
} elsif (-f _) {
|
||||
my $sha = Digest::SHA->new(256);
|
||||
eval { $sha->addfile($path, 'b'); 1 } or fail("cannot read $path: $@");
|
||||
$entries{$relative} = ['file', $sha->hexdigest];
|
||||
} else {
|
||||
$entries{$relative} = ['other', '-'];
|
||||
}
|
||||
},
|
||||
}, $dir);
|
||||
return \%entries;
|
||||
}
|
||||
|
||||
sub read_manifest {
|
||||
my ($file) = @_;
|
||||
open(my $fh, '<', $file) or fail("cannot read $file: $!");
|
||||
my %entries;
|
||||
while (my $line = <$fh>) {
|
||||
chomp $line;
|
||||
next if $line =~ /^\s*(?:#|$)/;
|
||||
my ($type, $value, $path) = split(/\t/, $line, 3);
|
||||
fail("$file line $.: malformed entry")
|
||||
if !defined $path || $path eq '' || $type !~ /^(?:file|link|dir)$/
|
||||
|| ($type eq 'file' && $value !~ /^[0-9a-f]{64}$/);
|
||||
fail("$file line $.: duplicate path $path") if exists $entries{$path};
|
||||
$entries{$path} = [$type, $value];
|
||||
}
|
||||
close($fh);
|
||||
return \%entries;
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
package XCAT::NFSLock;
|
||||
|
||||
# NFS lock protocol
|
||||
#
|
||||
# A lock on a shared, possibly re-exported, NFS tree. flock and fcntl are not
|
||||
# available there. The protocol uses mkdir, rmdir, unlink and plain file
|
||||
# writes. It uses no rename.
|
||||
#
|
||||
# Names in this module
|
||||
# lock.d the lock path given to acquire
|
||||
# lock.d/metadata the metadata of the owner
|
||||
# lock.borrow <lock path>.borrow, beside lock.d
|
||||
# R, T, δ the options retries, delay and jitter
|
||||
#
|
||||
# Assumptions
|
||||
# 1. mkdir(path) is atomic and exclusive among contenders, and successful
|
||||
# namespace changes eventually become visible.
|
||||
# 2. machine-id is unique among participating hosts.
|
||||
# Cloned VMs and images can share it by accident unless it is regenerated.
|
||||
# 3. Metadata writes eventually become readable completely and consistently.
|
||||
# 4. A host never declares one of its own live process incarnations dead.
|
||||
# 5. A process cannot die:
|
||||
# - after it creates lock.d, until it publishes valid metadata;
|
||||
# - while it holds lock.borrow, until it removes it.
|
||||
# 6. A crashed worker leaves recoverable state. The owning host eventually
|
||||
# returns and retries. Eventually one worker and its release complete.
|
||||
# 7. All participants follow the protocol.
|
||||
#
|
||||
# Metadata
|
||||
# lock.d/metadata contains:
|
||||
# machine-id, boot-id, pid, pstart, token, hash
|
||||
#
|
||||
# Ownership identity: (machine-id, boot-id, pid, pstart, token)
|
||||
# token is random per acquisition.
|
||||
# hash = HASH(canonical(SORT(k, v))) over all fields except hash.
|
||||
#
|
||||
# If the metadata is missing, cannot be parsed or hashed, or the hash does not
|
||||
# match, assume a partial or inconsistent read and retry. Never infer stale
|
||||
# ownership from invalid metadata.
|
||||
#
|
||||
# Retry
|
||||
# R = max retries, T = base delay, δ = jitter
|
||||
# R > 0, δ >= 0, T >= 3, T > 2δ
|
||||
#
|
||||
# Generic retry:
|
||||
# if retries >= R: fail
|
||||
# sleep(T + rand(-δ, δ))
|
||||
# retries++
|
||||
# goto 1
|
||||
#
|
||||
# Protocol
|
||||
# 1. mkdir lock.d
|
||||
# - success: write valid metadata, go to 8
|
||||
# - EEXIST: continue
|
||||
# - other error: fail
|
||||
# 2. Read and validate the metadata.
|
||||
# - invalid or missing: retry
|
||||
# - different machine-id: retry
|
||||
# - same host: save the observed ownership identity
|
||||
# 3. mkdir lock.borrow
|
||||
# - failure: retry
|
||||
# 4. Read and validate the metadata again.
|
||||
# - invalid, missing, or ownership identity changed: rmdir lock.borrow, retry
|
||||
# 5. Prove that the recorded (boot-id, pid, pstart) is dead.
|
||||
# - not provably dead: rmdir lock.borrow, retry
|
||||
# - dead: continue
|
||||
# 6. Replace the metadata with the identity of this process and a fresh token.
|
||||
# 7. rmdir lock.borrow
|
||||
# 8. Call the worker.
|
||||
# 9. mkdir lock.borrow
|
||||
# - failure: sleep(T + rand(-δ, δ)), retry step 9
|
||||
# 10. unlink lock.d/metadata
|
||||
# 11. rmdir lock.d (the actual unlock)
|
||||
# 12. rmdir lock.borrow
|
||||
#
|
||||
# Core invariants
|
||||
# lock.d exists => locked
|
||||
# lock.d absent => acquirable
|
||||
# invalid metadata => retry only
|
||||
# different machine-id => never recover here
|
||||
# lock.borrow exists => ownership transition or release in progress
|
||||
#
|
||||
# Steps 1 to 7 are acquire, step 8 is the caller, steps 9 to 12 are release.
|
||||
# release does steps 10 and 11 only when the metadata names this acquisition.
|
||||
#
|
||||
# Log
|
||||
# Unless quiet => 1, each lock event prints one line to the selected output handle:
|
||||
# [nfslock] <UTC time> <host> pid=<pid> <event> <label> <lock.d> [detail]
|
||||
# Events: acquired (step 1), took-over (step 6), wait (a retry), released (step 11),
|
||||
# release-skipped. The time has milliseconds, so the lines of two hosts sort into one order.
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Cwd qw(abs_path);
|
||||
use Digest::SHA qw(sha256_hex);
|
||||
use Errno qw(EEXIST ENOENT);
|
||||
use Exporter 'import';
|
||||
use File::Basename qw(dirname basename);
|
||||
use POSIX qw(strftime);
|
||||
use Sys::Hostname qw(hostname);
|
||||
use Time::HiRes ();
|
||||
|
||||
our @EXPORT_OK = qw(this_process format_metadata parse_metadata owner_is_dead process_start);
|
||||
|
||||
my @IDENTITY = qw(machine-id boot-id pid pstart token);
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
|
||||
=head3 acquire
|
||||
|
||||
Descriptions:
|
||||
Take the lock at $path with steps 1 to 7 of the protocol.
|
||||
Arguments:
|
||||
$path: path of lock.d
|
||||
%opt:
|
||||
label => word for messages (default "lock")
|
||||
delay => T, seconds, 3 or more (default 3)
|
||||
jitter => δ, seconds, 0 or more and less than T/2 (default 0.5)
|
||||
retries => R, more than 0 (default: timeout / T, at least 1)
|
||||
timeout => seconds to wait, used when retries is not given (default 0)
|
||||
quiet => 1 to print no log lines for this lock
|
||||
Returns:
|
||||
A lock object. Dies after R retries, naming the lock and its owner.
|
||||
|
||||
=cut
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
sub acquire {
|
||||
my ($class, $path, %opt) = @_;
|
||||
my $label = $opt{label} // 'lock';
|
||||
my $delay = $opt{delay} // 3;
|
||||
my $jitter = $opt{jitter} // 0.5;
|
||||
die "Invalid delay $delay for $label: must be 3s or more\n" unless $delay >= 3;
|
||||
die "Invalid jitter $jitter for $label: must be 0 or more\n" unless $jitter >= 0;
|
||||
die "Invalid jitter $jitter for $label: must be less than half the delay\n"
|
||||
unless $delay > 2 * $jitter;
|
||||
my $retries = $opt{retries};
|
||||
unless (defined($retries)) {
|
||||
my $timeout = $opt{timeout} // 0;
|
||||
$retries = int($timeout / $delay);
|
||||
$retries++ if $retries * $delay < $timeout;
|
||||
$retries = 1 if $retries < 1;
|
||||
}
|
||||
die "Invalid retries $retries for $label: must be a whole number more than 0\n"
|
||||
unless $retries =~ /\A[1-9][0-9]*\z/;
|
||||
|
||||
# The last component names the lock itself. basename would turn '' into './' and drop a
|
||||
# trailing slash, so the raw path is checked.
|
||||
my ($name) = ($path // '') =~ m{(?:\A|/)([^/]+)\z};
|
||||
die "Invalid $label path '" . ($path // '') . "'\n"
|
||||
if !defined($name) || $name eq '.' || $name eq '..';
|
||||
my $abs = _absolute($path);
|
||||
my $borrow = "$abs.borrow";
|
||||
my $self = bless {
|
||||
path => $abs,
|
||||
label => $label,
|
||||
pid => $$,
|
||||
delay => $delay,
|
||||
jitter => $jitter,
|
||||
quiet => $opt{quiet} ? 1 : 0,
|
||||
}, $class;
|
||||
my $here = _here();
|
||||
my $seen;
|
||||
|
||||
for (my $count = 0 ; ; $count++) {
|
||||
# Step 1.
|
||||
if (mkdir($abs)) {
|
||||
$self->{identity} = this_process();
|
||||
if (eval { _write_metadata($abs, $self->{identity}); 1 }) {
|
||||
$self->_log('acquired');
|
||||
return $self;
|
||||
}
|
||||
my $error = $@;
|
||||
unlink("$abs/metadata");
|
||||
rmdir($abs);
|
||||
die $error;
|
||||
}
|
||||
die "Cannot create $label $abs: $!\n" unless $! == EEXIST;
|
||||
|
||||
# Step 2.
|
||||
my $observed = _read_metadata($abs);
|
||||
$seen = $observed if defined($observed);
|
||||
if (defined($observed) && $observed->{'machine-id'} eq $here->{'machine-id'} && mkdir($borrow)) {
|
||||
# Steps 3 to 7.
|
||||
my $taken = eval {
|
||||
my $again = _read_metadata($abs);
|
||||
return 0 unless defined($again) && _key($again) eq _key($observed);
|
||||
return 0 unless owner_is_dead($again, $here);
|
||||
$self->{identity} = this_process();
|
||||
_write_metadata($abs, $self->{identity});
|
||||
1;
|
||||
};
|
||||
my $error = $@;
|
||||
rmdir($borrow);
|
||||
die $error unless defined($taken);
|
||||
if ($taken) {
|
||||
$self->_log('took-over', 'from dead pid ' . $observed->{pid});
|
||||
return $self;
|
||||
}
|
||||
}
|
||||
|
||||
last if $count >= $retries;
|
||||
$self->_log('wait', sprintf('retry %d/%d, owner %s', $count + 1, $retries, _describe($seen)));
|
||||
_sleep(_wait($delay, $jitter));
|
||||
}
|
||||
|
||||
my $who = _describe($seen);
|
||||
my $s = $retries == 1 ? 'retry' : 'retries';
|
||||
die "Trying to unlock $abs failed after $retries $s; $label owned by $who.\n"
|
||||
. "If you are sure it is safe, remove the lock: rm -rf $abs\n";
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
|
||||
=head3 release
|
||||
|
||||
Descriptions:
|
||||
Steps 9 to 12 of the protocol. Waits while another process holds
|
||||
lock.borrow. A forked child of the owner does nothing, and a lock whose
|
||||
metadata names another acquisition is left alone.
|
||||
Arguments:
|
||||
none
|
||||
Returns:
|
||||
1 when the lock was removed, 0 otherwise.
|
||||
|
||||
=cut
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
sub release {
|
||||
my ($self) = @_;
|
||||
return 0 if $self->{released} || $$ != $self->{pid};
|
||||
my $path = $self->{path};
|
||||
my $borrow = "$path.borrow";
|
||||
while (1) {
|
||||
# Step 9.
|
||||
if (mkdir($borrow)) {
|
||||
my $current = _read_metadata($path);
|
||||
if (defined($current) && _key($current) eq _key($self->{identity})) {
|
||||
$self->{released} = 1;
|
||||
unlink("$path/metadata");
|
||||
my $removed = rmdir($path);
|
||||
my $error = $!;
|
||||
rmdir($borrow);
|
||||
warn "Cannot remove $path: $error\n" unless $removed;
|
||||
$self->_log($removed ? 'released' : 'release-skipped', $removed ? () : ("rmdir: $error"));
|
||||
return $removed ? 1 : 0;
|
||||
}
|
||||
rmdir($borrow);
|
||||
# Valid metadata of another acquisition, or no lock.d at all: this lock is gone.
|
||||
if (defined($current) || !-d $path) {
|
||||
$self->{released} = 1;
|
||||
$self->_log('release-skipped', defined($current) ? 'owned by ' . _describe($current) : 'no lock.d');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
elsif ($! != EEXIST) {
|
||||
warn "Cannot create $borrow: $!\n";
|
||||
return 0;
|
||||
}
|
||||
_sleep(_wait($self->{delay}, $self->{jitter}));
|
||||
}
|
||||
}
|
||||
|
||||
sub path { return $_[0]{path} }
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
|
||||
=head3 this_process
|
||||
|
||||
Descriptions:
|
||||
The ownership identity of this process with a fresh token.
|
||||
Arguments:
|
||||
none
|
||||
Returns:
|
||||
A hash ref with machine-id, boot-id, pid, pstart and token.
|
||||
|
||||
=cut
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
sub this_process {
|
||||
my $here = _here();
|
||||
return {
|
||||
'machine-id' => $here->{'machine-id'},
|
||||
'boot-id' => $here->{'boot-id'},
|
||||
pid => $$,
|
||||
pstart => process_start($$) // die("Cannot read the start time of pid $$\n"),
|
||||
token => _token(),
|
||||
};
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
|
||||
=head3 format_metadata
|
||||
|
||||
Descriptions:
|
||||
The content of lock.d/metadata: one "key=value" line per field, sorted
|
||||
by key, and the hash line.
|
||||
Arguments:
|
||||
$fields: hash ref with machine-id, boot-id, pid, pstart and token
|
||||
Returns:
|
||||
The metadata string.
|
||||
|
||||
=cut
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
sub format_metadata {
|
||||
my ($fields) = @_;
|
||||
my $canonical = _canonical($fields);
|
||||
return $canonical . 'hash=' . sha256_hex($canonical) . "\n";
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
|
||||
=head3 parse_metadata
|
||||
|
||||
Descriptions:
|
||||
Validate the content of lock.d/metadata. A partial read, an unknown or
|
||||
repeated field, a malformed value and a hash that does not match all
|
||||
make the metadata invalid.
|
||||
Arguments:
|
||||
$text: the content of lock.d/metadata, or undef
|
||||
Returns:
|
||||
A hash ref of the identity fields, or undef when the metadata is invalid.
|
||||
|
||||
=cut
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
sub parse_metadata {
|
||||
my ($text) = @_;
|
||||
return undef unless defined($text) && $text =~ /\n\z/;
|
||||
my %field;
|
||||
for my $line (split(/\n/, $text)) {
|
||||
my ($key, $value) = $line =~ /\A([a-z-]+)=([^=\s]+)\z/ or return undef;
|
||||
return undef if exists($field{$key});
|
||||
$field{$key} = $value;
|
||||
}
|
||||
my $hash = delete($field{hash});
|
||||
return undef unless defined($hash) && keys(%field) == @IDENTITY;
|
||||
for my $key (@IDENTITY) {
|
||||
return undef unless defined($field{$key});
|
||||
}
|
||||
return undef unless $field{pid} =~ /\A[1-9][0-9]*\z/ && $field{pstart} =~ /\A[0-9]+\z/;
|
||||
return undef unless $hash eq sha256_hex(_canonical(\%field));
|
||||
return \%field;
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
|
||||
=head3 owner_is_dead
|
||||
|
||||
Descriptions:
|
||||
Step 5: decide from facts alone whether a recorded owner is dead. Only
|
||||
the owner's machine can know. Any other machine answers "not proven".
|
||||
Arguments:
|
||||
$owner: a hash ref from parse_metadata, or undef
|
||||
$here: hash ref with machine-id, boot-id and start_of, a code ref that
|
||||
returns the start time of a pid on this machine, or undef when
|
||||
no such process exists
|
||||
Returns:
|
||||
1 when the owner is provably dead, 0 otherwise.
|
||||
|
||||
=cut
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
sub owner_is_dead {
|
||||
my ($owner, $here) = @_;
|
||||
return 0 unless defined($owner);
|
||||
return 0 unless $owner->{'machine-id'} eq $here->{'machine-id'};
|
||||
return 1 unless $owner->{'boot-id'} eq $here->{'boot-id'};
|
||||
my $start = $here->{start_of}->($owner->{pid});
|
||||
return 1 unless defined($start);
|
||||
return $start eq $owner->{pstart} ? 0 : 1;
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
|
||||
=head3 process_start
|
||||
|
||||
Descriptions:
|
||||
The start time of a process, field 22 of /proc/<pid>/stat, in clock ticks
|
||||
since boot. A reused pid has a later start time.
|
||||
Arguments:
|
||||
$pid: the process id
|
||||
Returns:
|
||||
The start time, or undef when no such process exists.
|
||||
|
||||
=cut
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
sub process_start {
|
||||
my ($pid) = @_;
|
||||
open(my $fh, '<', "/proc/$pid/stat") or return undef;
|
||||
my $stat = <$fh>;
|
||||
close($fh);
|
||||
return undef unless defined($stat);
|
||||
# The command name, field 2, may contain spaces and parentheses.
|
||||
$stat =~ s/\A.*\)\s+//s or return undef;
|
||||
my @field = split(/\s+/, $stat);
|
||||
return $field[19];
|
||||
}
|
||||
|
||||
sub _log {
|
||||
my ($self, $event, $detail) = @_;
|
||||
return if $self->{quiet};
|
||||
my $now = Time::HiRes::time();
|
||||
my $time = strftime('%Y-%m-%dT%H:%M:%S', gmtime($now)) . sprintf('.%03dZ', ($now - int($now)) * 1000);
|
||||
my $host = (split(/\./, hostname() || 'unknown'))[0];
|
||||
my $line = join(' ', '[nfslock]', $time, $host, "pid=$$", $event, $self->{label}, $self->{path},
|
||||
defined($detail) ? $detail : ());
|
||||
print "$line\n";
|
||||
}
|
||||
|
||||
sub _canonical {
|
||||
my ($fields) = @_;
|
||||
return join('', map { "$_=$fields->{$_}\n" } sort @IDENTITY);
|
||||
}
|
||||
|
||||
sub _key {
|
||||
my ($fields) = @_;
|
||||
return join("\n", map { $fields->{$_} } @IDENTITY);
|
||||
}
|
||||
|
||||
# A reader can see this file half written. The hash makes that read invalid.
|
||||
sub _write_metadata {
|
||||
my ($path, $identity) = @_;
|
||||
my $file = "$path/metadata";
|
||||
my $fh;
|
||||
open($fh, '>', $file) && print({$fh} format_metadata($identity)) && close($fh)
|
||||
or die "Cannot write $file: $!\n";
|
||||
}
|
||||
|
||||
sub _read_metadata {
|
||||
my ($path) = @_;
|
||||
open(my $fh, '<', "$path/metadata") or return undef;
|
||||
local $/;
|
||||
my $text = <$fh>;
|
||||
close($fh);
|
||||
return parse_metadata($text);
|
||||
}
|
||||
|
||||
sub _wait {
|
||||
my ($delay, $jitter) = @_;
|
||||
return $delay + (2 * rand() - 1) * $jitter;
|
||||
}
|
||||
|
||||
sub _sleep {
|
||||
my ($seconds) = @_;
|
||||
Time::HiRes::sleep($seconds);
|
||||
}
|
||||
|
||||
sub _token {
|
||||
if (open(my $fh, '<:raw', '/dev/urandom')) {
|
||||
my $read = read($fh, my $bytes, 16);
|
||||
close($fh);
|
||||
return unpack('H*', $bytes) if defined($read) && $read == 16;
|
||||
}
|
||||
return join('', map { sprintf('%08x', int(rand(2**32))) } 1 .. 4);
|
||||
}
|
||||
|
||||
# Assumption 2 needs a real machine-id. A host without one cannot take part.
|
||||
sub _here {
|
||||
return {
|
||||
'machine-id' => _first_line('/etc/machine-id')
|
||||
// die("Cannot read /etc/machine-id: an NFS lock needs a machine id\n"),
|
||||
'boot-id' => _first_line('/proc/sys/kernel/random/boot_id')
|
||||
// die("Cannot read the boot id of this machine\n"),
|
||||
start_of => \&process_start,
|
||||
};
|
||||
}
|
||||
|
||||
sub _first_line {
|
||||
my ($file) = @_;
|
||||
open(my $fh, '<', $file) or return undef;
|
||||
my $line = <$fh>;
|
||||
close($fh);
|
||||
return undef unless defined($line);
|
||||
chomp($line);
|
||||
return length($line) ? $line : undef;
|
||||
}
|
||||
|
||||
sub _describe {
|
||||
my ($owner) = @_;
|
||||
return 'an unknown owner' unless defined($owner);
|
||||
return sprintf('pid %s on machine %s', $owner->{pid}, $owner->{'machine-id'});
|
||||
}
|
||||
|
||||
# An absolute path in the message, without resolving the lock itself.
|
||||
sub _absolute {
|
||||
my ($path) = @_;
|
||||
my $dir = abs_path(dirname($path)) // dirname($path);
|
||||
return "$dir/" . basename($path);
|
||||
}
|
||||
|
||||
1;
|
||||
+89
-114
@@ -15,7 +15,8 @@ use Parallel::ForkManager;
|
||||
use POSIX qw(strftime);
|
||||
use FindBin qw($RealBin);
|
||||
use lib $RealBin, "$RealBin/lib";
|
||||
use MockBuildUtils qw(sh_quote print_step version_matches required_pkgs rpm_in_cell
|
||||
use XCAT::NFSLock ();
|
||||
use MockBuildUtils qw(sh_quote print_step version_matches required_pkgs rpm_in_cell resolve_mock_cfg
|
||||
carry_over_rpms rpm_name rpm_arch rpm_source_rpm rpm_digests_ok
|
||||
install_deps_packages install_deps_command missing_perl_modules
|
||||
read_manifest verify_repo_packages verify_repo_signature verify_rpm_signatures
|
||||
@@ -130,10 +131,12 @@ my $gpg_sign = 0;
|
||||
my $gpg_key_name = 'xCAT Signing Key';
|
||||
my $gpg_home = '';
|
||||
my $gpg_program = '';
|
||||
my $force_unlock = 0;
|
||||
# Seconds to wait for a lock that a live process holds. 0 fails at once.
|
||||
my $try_unlock_timeout = 0;
|
||||
# --finalize-xcat-dep: post-build cross-arch genesis provisioning (issue #7610). Takes the two
|
||||
# per-arch repo roots and cross-populates the noarch xCAT-genesis-base between them.
|
||||
my $finalize_xcat_dep = 0;
|
||||
my @finalize_arch;
|
||||
my $x86_64_repo = '';
|
||||
my $ppc64le_repo = '';
|
||||
# --verify-repo=<repo>: standalone, build-free completeness + signature gate over one already-built
|
||||
@@ -159,8 +162,9 @@ GetOptions(
|
||||
'gpg-sign!' => \$gpg_sign,
|
||||
'gpg-key-name=s' => \$gpg_key_name,
|
||||
'gpg-home=s' => \$gpg_home,
|
||||
'force-unlock!' => \$force_unlock,
|
||||
'try-unlock-timeout=i' => \$try_unlock_timeout,
|
||||
'finalize-xcat-dep!' => \$finalize_xcat_dep,
|
||||
'finalize-arch=s' => \@finalize_arch,
|
||||
'x86_64-repo=s' => \$x86_64_repo,
|
||||
'ppc64le-repo=s' => \$ppc64le_repo,
|
||||
'verify-repo=s' => \$verify_repo,
|
||||
@@ -189,6 +193,7 @@ GetOptions(
|
||||
) or die usage();
|
||||
|
||||
die "Run as root (uid=$>)\n" if $> != 0 && !$finalize_xcat_dep && !$verify_repo;
|
||||
die "--try-unlock-timeout must be >= 0\n" if $try_unlock_timeout < 0;
|
||||
# --skip-build collects a prior build's artifacts from that build's per-target tree, so it must
|
||||
# know the target. Without --target the default is "all three EL targets", and each would collect
|
||||
# the same artifacts and cross-publish them into every repo (foreign-EL / foreign-arch rpms).
|
||||
@@ -253,9 +258,19 @@ if ($finalize_xcat_dep) {
|
||||
my $ppc = abs_path($ppc64le_repo) or die "--ppc64le-repo '$ppc64le_repo' not found\n";
|
||||
die "--x86_64-repo '$x86' is not a directory\n" if !-d $x86;
|
||||
die "--ppc64le-repo '$ppc' is not a directory\n" if !-d $ppc;
|
||||
# finalize rewrites the per-arch cells a build deploys, so it takes the same cell locks. With
|
||||
# --finalize-arch it writes, and locks, only the cells of those arches.
|
||||
@finalize_arch = map { split /[\s,]+/ } @finalize_arch;
|
||||
@finalize_arch = qw(x86_64 ppc64le) unless @finalize_arch;
|
||||
my %cell;
|
||||
for my $root ($x86, $ppc) {
|
||||
$cell{ abs_path($_) } = 1 for grep { -d } map { glob("$root/*/$_") } @finalize_arch;
|
||||
}
|
||||
take_lock(cell_lock_path($_), 'repository cell lock') for sort keys %cell;
|
||||
# Inject the per-rpm gpg re-sign and the repo re-index as callbacks so the finalize logic in
|
||||
# MockBuildUtils stays free of this script's gpg/createrepo state.
|
||||
finalize_xcat_dep($x86, $ppc,
|
||||
only => \@finalize_arch,
|
||||
sign => ($gpg_sign ? sub {
|
||||
my ($rpm) = @_;
|
||||
local $ENV{GNUPGHOME} = $gpg_home if $gpg_home;
|
||||
@@ -272,7 +287,7 @@ if ($finalize_xcat_dep) {
|
||||
unless ($no_verify_repo) {
|
||||
my %seen;
|
||||
for my $root ($x86, $ppc) {
|
||||
my @cells = (glob("$root/rh*/x86_64"), glob("$root/rh*/ppc64le"));
|
||||
my @cells = map { glob("$root/rh*/$_") } @finalize_arch;
|
||||
for my $d (sort @cells) {
|
||||
next unless -d $d;
|
||||
my $abs = abs_path($d);
|
||||
@@ -320,11 +335,10 @@ make_path($repo_dep) if !-d $repo_dep;
|
||||
$repo_dep = abs_path($repo_dep)
|
||||
or die "Cannot resolve --repo-dep directory\n";
|
||||
|
||||
# Fail-fast lock on the output base so a second run against the same --output aborts instead of
|
||||
# racing on the shared NFS tree. Held for the whole invocation; released by the exit handlers.
|
||||
acquire_output_lock($output_base, $force_unlock);
|
||||
acquire_repository_lock($repo_dep, $force_unlock)
|
||||
if $repo_dep ne $output_base;
|
||||
# Locks are taken in one order: the output tree, then each repository cell in name order, then
|
||||
# common. The exit handlers release them.
|
||||
take_lock("$output_base/.lock", 'output lock');
|
||||
MockBuildUtils::recover_common_repository($repo_dep) unless $dry_run;
|
||||
|
||||
$xcat_src = resolve_xcat_source($xcat_src, $repo_root);
|
||||
|
||||
@@ -412,12 +426,22 @@ my %forcearch_targets = (
|
||||
arch => 'riscv64',
|
||||
# x86_64 only, as the mock config admits: syslinux-xcat builds on x86 and ppc64le alone.
|
||||
noarch_cfg => 'rocky-10-x86_64',
|
||||
dep_builders => [qw(elilo-xcat grub2-xcat ipmitool-xcat syslinux-xcat goconserver conserver-xcat xnba-undi)],
|
||||
required => [qw(ipmitool-xcat syslinux-xcat grub2-xcat xnba-undi
|
||||
dep_builders => [qw(elilo-xcat grub2-xcat ipmitool-xcat syslinux-xcat goconserver conserver-xcat xnba-undi ipxe-xcat)],
|
||||
required => [qw(ipmitool-xcat syslinux-xcat grub2-xcat xnba-undi ipxe-xcat
|
||||
perl-IO-Stty perl-HTTP-Async perl-Net-HTTPS-NB)],
|
||||
},
|
||||
);
|
||||
|
||||
# Each target deploys one cell, <repo-dep>/rh<rel>/<arch>, and locks only that cell: the per-arch
|
||||
# runs of one build share --repo-dep and never wait on each other.
|
||||
my @cell_locks = map {
|
||||
my $cell = target_cell($_);
|
||||
make_path(dirname($cell));
|
||||
cell_lock_path($cell);
|
||||
} @build_targets;
|
||||
my %cell_lock_seen;
|
||||
take_lock($_, 'repository cell lock') for grep { !$cell_lock_seen{$_}++ } sort @cell_locks;
|
||||
|
||||
# NOTE: no dhcp- packages are built here. DHCP backend selection is an install-time
|
||||
# rich dep in xCAT.spec (kea if system-release>=10 else /usr/sbin/dhcpd), so there is
|
||||
# nothing arch/EL-specific to build or to exclude for el10.
|
||||
@@ -426,8 +450,7 @@ print_step('Targets to build');
|
||||
print " $_\n" for @build_targets;
|
||||
print "output_base: $output_base\n";
|
||||
print "deploy repo-dep: $repo_dep\n";
|
||||
print "output lock: $output_base/.lock (held)\n";
|
||||
print "repository lock: $repo_dep/.lock (held)\n";
|
||||
print "locks held: $_\n" for map { $_->path } @HELD_LOCKS;
|
||||
print "gpg_sign: $gpg_sign\n";
|
||||
print "gpg_key_name: $gpg_key_name\n" if $gpg_sign;
|
||||
print "gpg_home: " . ($gpg_home ne '' ? $gpg_home : '(default keyring)') . "\n" if $gpg_sign;
|
||||
@@ -536,10 +559,11 @@ if (!$skip_build && !$dry_run && -d $run_root) {
|
||||
remove_tree($run_root);
|
||||
}
|
||||
|
||||
# All dep builders run natively on every arch. xnba-undi and grub2-xcat are noarch packagings of
|
||||
# committed artifacts (an x86 UNDI ROM / the grub2 resource tarball) with no arch-specific build
|
||||
# step, so ppc builds them the same as x86 -- no cross-arch import. A forcearch target builds
|
||||
# only the builders its profile lists; the noarch ones run in the profile's native chroot.
|
||||
# All dep builders run natively on every arch. xnba-undi, grub2-xcat and ipxe-xcat are noarch
|
||||
# packagings of committed artifacts (an x86 UNDI ROM / the grub2 resource tarball / the iPXE release
|
||||
# tree) with no arch-specific build step, so ppc builds them the same as x86 -- no cross-arch
|
||||
# import. A forcearch target builds only the builders its profile lists; the noarch ones run in
|
||||
# the profile's native chroot.
|
||||
# syslinux-xcat is noarch too, and its spec builds on x86 and ppc64le only.
|
||||
my @dep_builders = (
|
||||
{ name => 'elilo-xcat', script => "$repo_root/elilo/mockbuild.pl", noarch => 1 },
|
||||
@@ -549,6 +573,7 @@ my @dep_builders = (
|
||||
{ name => 'goconserver', script => "$repo_root/goconserver/mockbuild.pl" },
|
||||
{ name => 'conserver-xcat', script => "$repo_root/conserver/mockbuild.pl" },
|
||||
{ name => 'xnba-undi', script => "$repo_root/xnba/mockbuild.pl", noarch => 1 },
|
||||
{ name => 'ipxe-xcat', script => "$repo_root/ipxe-xcat/mockbuild.pl", noarch => 1 },
|
||||
);
|
||||
my %profile_builds = map { $_ => 1 } @{ $profile->{dep_builders} };
|
||||
|
||||
@@ -887,7 +912,7 @@ if (!$skip_genesis && !$dry_run) {
|
||||
# A skipped builder built nothing this run, so everything it published in the cell joins the run
|
||||
# repository here, ahead of the bump check, createrepo, the tarballs and the deploy gate.
|
||||
if (!$dry_run && ($skip_genesis || $skip_perl || $skip_xcat_dep)) {
|
||||
my $published = "$repo_dep/rh$rel/$arch";
|
||||
my $published = target_cell($target);
|
||||
if (-d $published) {
|
||||
my %skipped = (genesis => $skip_genesis, perl => $skip_perl, dep => $skip_xcat_dep);
|
||||
# Only an rpm the configured key signed, by signer id and by rpmkeys --checksig, may be
|
||||
@@ -1040,11 +1065,11 @@ sub target_profile {
|
||||
noarch_cfg => $target,
|
||||
forcearch => 0,
|
||||
epel => 1,
|
||||
dep_builders => [qw(elilo-xcat grub2-xcat ipmitool-xcat syslinux-xcat goconserver conserver-xcat xnba-undi)],
|
||||
dep_builders => [qw(elilo-xcat grub2-xcat ipmitool-xcat syslinux-xcat goconserver conserver-xcat xnba-undi ipxe-xcat)],
|
||||
# xCAT Requires all of these on every arch, and every one of them builds natively on
|
||||
# every arch (the noarch deps -- grub2-xcat, xnba-undi -- just repackage committed
|
||||
# every arch (the noarch deps -- grub2-xcat, xnba-undi, ipxe-xcat -- just repackage committed
|
||||
# artifacts), so a self-sufficient per-arch build produces the whole set.
|
||||
required => [qw(ipmitool-xcat syslinux-xcat grub2-xcat xnba-undi
|
||||
required => [qw(ipmitool-xcat syslinux-xcat grub2-xcat xnba-undi ipxe-xcat
|
||||
perl-IO-Stty perl-HTTP-Async perl-Net-HTTPS-NB)],
|
||||
};
|
||||
}
|
||||
@@ -1077,7 +1102,7 @@ sub deploy_target {
|
||||
my $rel = $info->{rel};
|
||||
my $src = $info->{repo_dir};
|
||||
my $tarch = $info->{profile}{arch};
|
||||
my $dest = "$repo_dep/rh$rel/$tarch";
|
||||
my $dest = target_cell($tgt);
|
||||
print_step("Deploy $tgt -> $dest");
|
||||
return if $dry_run;
|
||||
|
||||
@@ -1148,6 +1173,8 @@ sub publish_genesis_common_repo {
|
||||
return;
|
||||
}
|
||||
|
||||
# Every architecture run can publish common, so common has its own lock.
|
||||
take_lock("$repo_dep/.common-publish.lock", 'common lock');
|
||||
$COMMON_STAGE = tempdir('.common.XXXXXXXX', DIR => $repo_dep, CLEANUP => 0);
|
||||
my $published = publish_genesis_release_packages('rpm', $COMMON_STAGE);
|
||||
verify_genesis_release_packages('rpm', $COMMON_STAGE);
|
||||
@@ -1259,12 +1286,20 @@ sub publish_file {
|
||||
die $error;
|
||||
}
|
||||
|
||||
# createrepo_c command with upstream-matching, deterministic metadata. The tool's
|
||||
# defaults emit primary/filelists/other as *.xml.zst plus *.sqlite.bz2 (--database),
|
||||
# exactly the upstream shape; --set-timestamp-to-revision pins repomd to SOURCE_DATE_EPOCH.
|
||||
# createrepo_c command with deterministic metadata: primary/filelists/other as *.xml.zst, with
|
||||
# --set-timestamp-to-revision pinning repomd to SOURCE_DATE_EPOCH.
|
||||
#
|
||||
# NO --database. It writes *.sqlite.bz2, and SQLite needs POSIX locks. The build tree lives on the
|
||||
# shared shared tree, an NFS mount the hypervisor re-exports, and the kernel refuses locks there:
|
||||
# every attempt answers errno 524. createrepo_c died with "Cannot open .repodata/primary.sqlite:
|
||||
# Can not create db_info table: disk I/O error" on every target once the build hosts moved off
|
||||
# virtiofs -- xcat-dep-el-cd #161 and #162 both failed that way and staged nothing.
|
||||
#
|
||||
# --database is deprecated in createrepo_c 1.1.2 and --no-database is its default. dnf on el8+ and
|
||||
# zypper read the XML.
|
||||
sub createrepo_c_cmd {
|
||||
my ($dir) = @_;
|
||||
return 'createrepo_c --update --database '
|
||||
return 'createrepo_c --update '
|
||||
. '--revision ' . shell_quote($SOURCE_DATE_EPOCH) . ' --set-timestamp-to-revision '
|
||||
. shell_quote($dir);
|
||||
}
|
||||
@@ -1417,7 +1452,11 @@ Options:
|
||||
--output-root PATH Override the derived build tree root (default: <output>/mockbuild-all)
|
||||
--repo-dep PATH Override the deployable output root; rh8/rh9/rh10/<arch> and common
|
||||
are assembled and signed here (default: <output>/xcat-dep)
|
||||
--force-unlock Remove a stale <output>/.lock before acquiring it
|
||||
--try-unlock-timeout N Wait about N seconds (default 0: one retry of 3s) for a lock
|
||||
that a live process holds, then fail with the command that
|
||||
removes it. A lock whose owner is proven dead on this host
|
||||
is taken over at once. Locks: <output>/.lock, one <repo-dep>/rh<N>/.<arch>.lock
|
||||
per target, and <repo-dep>/.common-publish.lock for common
|
||||
--finalize-xcat-dep Post-build cross-arch genesis mode (builds nothing). Requires
|
||||
--x86_64-repo and --ppc64le-repo. For each matching <os>/x86_64 and
|
||||
<os>/ppc64le repo pair, copies the noarch xCAT-genesis-base-ppc64
|
||||
@@ -1426,6 +1465,9 @@ Options:
|
||||
ppc64le repo (dropping any stale foreign-arch genesis), then
|
||||
re-indexes + re-signs. Restores the 2.17 cross-arch genesis
|
||||
(issue #7610). Honors --gpg-sign/--gpg-key-name/--gpg-home. Use alone.
|
||||
--finalize-arch ARCH (finalize) Write, lock and verify only the cells of ARCH (x86_64 or
|
||||
ppc64le; repeatable). Run it on the host that builds ARCH, so a
|
||||
dead finalize leaves locks that host can reclaim. Default: both
|
||||
--x86_64-repo PATH (finalize) x86_64 repo root holding <os>/x86_64 (e.g. rh9/x86_64)
|
||||
--ppc64le-repo PATH (finalize) ppc64le repo root holding <os>/ppc64le
|
||||
--verify-repo PATH Standalone completeness + signature gate over the per-target repo at PATH
|
||||
@@ -2129,28 +2171,6 @@ sub collect_srpms {
|
||||
return ($copied, $skipped_non_src, $missing_roots);
|
||||
}
|
||||
|
||||
sub resolve_mock_cfg {
|
||||
my ($os_id, $rel, $arch) = @_;
|
||||
my %short_forms = (
|
||||
almalinux => 'alma',
|
||||
'centos-stream' => 'centos-stream',
|
||||
rocky => 'rocky',
|
||||
);
|
||||
# Resolve by CONFIG-FILE existence, not by running `mock --print-root-path`: the latter can fail
|
||||
# transiently (bootstrap chroot setup, a concurrent mock holding a lock) and made el10 flakily
|
||||
# "resolve" to the long form that has no .cfg. Checking /etc/mock/<cfg>.cfg is deterministic.
|
||||
for my $id ($os_id, (exists $short_forms{$os_id} ? ($short_forms{$os_id}) : ())) {
|
||||
my $candidate = "${id}+epel-${rel}-${arch}";
|
||||
if (-f "/etc/mock/${candidate}.cfg") {
|
||||
print "Mock config resolved: $candidate\n" if $id ne $os_id;
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
my $short = $short_forms{$os_id} // $os_id;
|
||||
die "Could not find mock config for ${os_id}+epel-${rel}-${arch} "
|
||||
. "(tried /etc/mock/${os_id}+epel-${rel}-${arch}.cfg and /etc/mock/${short}+epel-${rel}-${arch}.cfg)\n";
|
||||
}
|
||||
|
||||
sub resolve_xcat_source {
|
||||
my ($requested, $root) = @_;
|
||||
# Prefer the sibling ../xcat-core (the real layout: source/xcat-core beside source/xcat-dep)
|
||||
@@ -2169,72 +2189,29 @@ sub resolve_xcat_source {
|
||||
return eval { abs_path($requested) } || $requested;
|
||||
}
|
||||
|
||||
# Fail-fast advisory lock on the output base. Uses an atomic mkdir (portable and reliable over
|
||||
# NFS, unlike flock) of "<base>/.lock". A second run against the same --output dies immediately
|
||||
# rather than racing on the shared tree. Only the process that created the lock removes it.
|
||||
sub acquire_named_lock {
|
||||
my ($base, $label, $force) = @_;
|
||||
my $lock = "$base/.lock";
|
||||
if ($force && -d $lock) {
|
||||
print "force-unlock: removing stale lock $lock\n";
|
||||
_rmdir_lock($lock);
|
||||
}
|
||||
if (mkdir $lock) {
|
||||
push(@HELD_LOCKS, $lock);
|
||||
$LOCK_OWNER_PID //= $$;
|
||||
my $host = capture_command('uname', '-n') || 'unknown';
|
||||
if (open my $fh, '>', "$lock/owner") {
|
||||
print {$fh} "host=$host\npid=$$\nepoch=" . time() . "\n";
|
||||
close $fh;
|
||||
}
|
||||
return;
|
||||
}
|
||||
# mkdir failed: either it already exists (locked) or a real error.
|
||||
if (-d $lock) {
|
||||
my $info = '';
|
||||
if (open my $fh, '<', "$lock/owner") { local $/; $info = <$fh>; close $fh; }
|
||||
$info =~ s/\s+/ /g;
|
||||
die "$label $base is locked ($lock): $info\n"
|
||||
. "another mockbuild-all run owns it; use a different destination or --force-unlock if stale.\n";
|
||||
}
|
||||
die "Cannot create lock $lock: $!\n";
|
||||
# A lock the run holds until it exits. The owner pid lets the exit handlers tell the run from the
|
||||
# build workers it forks, which inherit the lock list.
|
||||
sub take_lock {
|
||||
my ($path, $label) = @_;
|
||||
my $lock = XCAT::NFSLock->acquire($path, timeout => $try_unlock_timeout, label => $label);
|
||||
push(@HELD_LOCKS, $lock);
|
||||
$LOCK_OWNER_PID //= $$;
|
||||
return $lock;
|
||||
}
|
||||
|
||||
sub acquire_output_lock {
|
||||
my ($base, $force) = @_;
|
||||
acquire_named_lock($base, 'output', $force);
|
||||
# The repository cell a target deploys. The deploy, the carry-over and the cell lock all take the
|
||||
# path from here, so the lock covers the directory the deploy writes.
|
||||
sub target_cell {
|
||||
my ($target) = @_;
|
||||
my $profile = target_profile($target);
|
||||
return "$repo_dep/rh$profile->{rel}/$profile->{arch}";
|
||||
}
|
||||
|
||||
sub acquire_repository_lock {
|
||||
my ($base, $force) = @_;
|
||||
_recover_common_repository($base) if $force;
|
||||
acquire_named_lock($base, 'repository', $force);
|
||||
}
|
||||
|
||||
sub _recover_common_repository {
|
||||
my ($base) = @_;
|
||||
my $destination = "$base/common";
|
||||
my @backups = sort {
|
||||
((stat($a))[9] // 0) <=> ((stat($b))[9] // 0)
|
||||
} grep { -d $_ && !-l $_ } bsd_glob("$base/.common.previous.*");
|
||||
|
||||
if (!-e $destination && !-l $destination && @backups) {
|
||||
my $backup = pop(@backups);
|
||||
rename($backup, $destination)
|
||||
or die "Cannot restore interrupted common repository $backup: $!\n";
|
||||
}
|
||||
remove_tree($_) for grep { -d $_ && !-l $_ } @backups;
|
||||
|
||||
for my $staging (bsd_glob("$base/.common.*")) {
|
||||
next if $staging =~ m{/\.common\.previous\.};
|
||||
remove_tree($staging) if -d $staging && !-l $staging;
|
||||
}
|
||||
}
|
||||
|
||||
sub _rmdir_lock {
|
||||
my ($lock) = @_;
|
||||
unlink "$lock/owner";
|
||||
rmdir $lock;
|
||||
# The lock of a repository cell <dir>/<arch> sits beside it, as <dir>/.<arch>.lock: the deploy
|
||||
# replaces the cell directory itself.
|
||||
sub cell_lock_path {
|
||||
my ($cell) = @_;
|
||||
return dirname($cell) . '/.' . basename($cell) . '.lock';
|
||||
}
|
||||
|
||||
# Release locks on every exit path, but only from the process that acquired them.
|
||||
@@ -2254,9 +2231,7 @@ sub _restore_common_repository {
|
||||
|
||||
sub _release_locks_if_owner {
|
||||
return unless defined($LOCK_OWNER_PID) && $$ == $LOCK_OWNER_PID;
|
||||
for my $lock (reverse(@HELD_LOCKS)) {
|
||||
_rmdir_lock($lock) if -d $lock;
|
||||
}
|
||||
$_->release for reverse(@HELD_LOCKS);
|
||||
}
|
||||
END {
|
||||
_restore_common_repository();
|
||||
|
||||
@@ -61,6 +61,7 @@ elilo-xcat=3.14
|
||||
goconserver=>= 0.3.3-snap202011021058
|
||||
grub2-xcat=1.0
|
||||
ipmitool-xcat=>= 1.8.18-4
|
||||
ipxe-xcat=2.0.0
|
||||
syslinux-xcat=>= 6.03-1
|
||||
xnba-undi=>= 1.21.1-1
|
||||
perl-HTML-Form=6.07
|
||||
@@ -75,6 +76,7 @@ elilo-xcat=3.14
|
||||
goconserver=>= 0.3.3-snap202011021058
|
||||
grub2-xcat=1.0
|
||||
ipmitool-xcat=>= 1.8.18-4
|
||||
ipxe-xcat=2.0.0
|
||||
syslinux-xcat=>= 6.03-1
|
||||
xnba-undi=>= 1.21.1-1
|
||||
perl-HTML-Form=6.07
|
||||
@@ -89,6 +91,7 @@ elilo-xcat=3.14
|
||||
goconserver=>= 0.3.3-snap202011021058
|
||||
grub2-xcat=1.0
|
||||
ipmitool-xcat=>= 1.8.18-4
|
||||
ipxe-xcat=2.0.0
|
||||
syslinux-xcat=>= 6.03-1
|
||||
xnba-undi=>= 1.21.1-1
|
||||
perl-HTTP-Async=>= 0.30-3
|
||||
@@ -103,6 +106,7 @@ elilo-xcat=3.14
|
||||
goconserver=>= 0.3.3-snap202011021058
|
||||
grub2-xcat=1.0
|
||||
ipmitool-xcat=>= 1.8.18-4
|
||||
ipxe-xcat=2.0.0
|
||||
syslinux-xcat=>= 6.03-1
|
||||
xnba-undi=>= 1.21.1-1
|
||||
perl-HTTP-Async=>= 0.30-3
|
||||
@@ -117,6 +121,7 @@ elilo-xcat=3.14
|
||||
goconserver=>= 0.3.3-snap202011021058
|
||||
grub2-xcat=1.0
|
||||
ipmitool-xcat=>= 1.8.18-4
|
||||
ipxe-xcat=2.0.0
|
||||
syslinux-xcat=>= 6.03-1
|
||||
xnba-undi=>= 1.21.1-1
|
||||
perl-Crypt-SSLeay=0.72
|
||||
@@ -133,6 +138,7 @@ elilo-xcat=3.14
|
||||
goconserver=>= 0.3.3-snap202011021058
|
||||
grub2-xcat=1.0
|
||||
ipmitool-xcat=>= 1.8.18-4
|
||||
ipxe-xcat=2.0.0
|
||||
syslinux-xcat=>= 6.03-1
|
||||
xnba-undi=>= 1.21.1-1
|
||||
perl-Crypt-SSLeay=0.72
|
||||
@@ -147,7 +153,7 @@ xCAT-genesis-base=>= 2:2.18.0
|
||||
# EPEL-fed EL10 sections above in one way (see BUILD.md, "riscv64"): riscv64 has no EPEL, so the
|
||||
# perl deps EL10 otherwise takes from EPEL are built here too (perl-Crypt-Blowfish ...
|
||||
# perl-Path-Class below). perl-Path-Class is a build dep of perl-Crypt-SSLeay only. The x86 boot
|
||||
# loaders (elilo-xcat, syslinux-xcat, xnba-undi) are noarch and are listed like on ppc64le: a
|
||||
# loaders (elilo-xcat, ipxe-xcat, syslinux-xcat, xnba-undi) are noarch and are listed like on ppc64le: a
|
||||
# riscv64 management node serves the x86 nodes of a mixed cluster.
|
||||
# The per-EL perl set is the EL10 one plus perl-HTML-Form: EPEL supplies it on the other
|
||||
# architectures, nothing supplies it on riscv64, and perl-xCAT requires perl(HTML::Form).
|
||||
@@ -158,6 +164,7 @@ elilo-xcat=3.14
|
||||
goconserver=>= 0.3.3-snap202011021058
|
||||
grub2-xcat=1.0
|
||||
ipmitool-xcat=>= 1.8.18-4
|
||||
ipxe-xcat=2.0.0
|
||||
syslinux-xcat=>= 6.03-1
|
||||
xnba-undi=>= 1.21.1-1
|
||||
perl-Crypt-SSLeay=0.72
|
||||
|
||||
+70
-140
@@ -5,7 +5,7 @@
|
||||
# * mk-dep-chroots.sh -> the "ensure chroots" phase (auto-initializes per-codename sbuild chroots
|
||||
# on first run; idempotent).
|
||||
# * build-dep-debs.sh -> the per-package build phase (drives each <dep>/sbuild.pl in the matching
|
||||
# chroot) + the metadata-preserving genesis phase.
|
||||
# chroot) + the genesis-ingest phase.
|
||||
# * build-apt-repo.sh -> the apt-repo assembly + signing phase (in Perl, focal supported).
|
||||
#
|
||||
# Design (mirrors mockbuild-all.pl + fixes the PR #63 review):
|
||||
@@ -17,14 +17,14 @@
|
||||
# two arches build concurrently on their two hosts, so a per-arch build that also published would
|
||||
# interleave wipes of the same pool/dists/Release; and a partial or failed build must never reach
|
||||
# the published repo, nor stale debs accumulate in it (concern #1).
|
||||
# 3. Per-arch package sets come from the manifest: the x86 boot components (syslinux/elilo/xnba,
|
||||
# 3. Per-arch package sets come from the manifest: the x86 boot components (syslinux/elilo/xnba/ipxe-xcat,
|
||||
# Architecture:all) are built once on amd64 (single producer); ppc64el builds only the genuinely
|
||||
# arch-specific compiled deps (concern #3).
|
||||
# 4. Any required chroot / package / artifact failure, or any version-pin mismatch, fails the whole
|
||||
# run non-zero (concern #4).
|
||||
# 5. The genesis-base deb keeps the maintained Debian packaging semantics -- a native deb is ingested
|
||||
# as-is when provided; a converted rpm keeps the maintained control (Depends/Breaks/Replaces) and
|
||||
# maintainer scripts (concern #2).
|
||||
# 5. The genesis-base debs are built by xcat-core, one per Ubuntu codename, and are ingested here
|
||||
# as they are. Each is staged into the suite it was built for: the image carries the kernel of
|
||||
# the root that built it, so one image cannot serve several releases.
|
||||
use strict;
|
||||
use warnings;
|
||||
use Cwd qw(abs_path);
|
||||
@@ -36,9 +36,9 @@ use File::Temp qw(tempdir);
|
||||
use Getopt::Long qw(GetOptions);
|
||||
use Pod::Usage qw(pod2usage);
|
||||
use POSIX qw(strftime);
|
||||
use Fcntl qw(:flock);
|
||||
use FindBin qw($RealBin);
|
||||
use lib $RealBin, "$RealBin/lib";
|
||||
use XCAT::NFSLock ();
|
||||
# NOTE: XCAT::GenesisRelease (the shared reader/validator of an OpenEmbedded Genesis package release,
|
||||
# the same module mockbuild-all.pl uses for the rpm side) is loaded ON DEMAND in the --genesis-release
|
||||
# block, NOT with `use` here. It pulls in XCAT::BuildUtils, which needs File::Slurper, and the Ubuntu
|
||||
@@ -53,12 +53,11 @@ use BuildUtils qw(sh_quote print_step version_matches required_pkgs read_manifes
|
||||
codename_to_version known_codenames supported_arches is_supported_arch
|
||||
chroot_name chroot_sources_list
|
||||
chroot_is_disposable
|
||||
control_field genesis_deb_control
|
||||
control_field
|
||||
deb_field deb_version deb_hash cross_copy_genesis_deb);
|
||||
|
||||
my $script_dir = abs_path(dirname(__FILE__));
|
||||
my $repo_root = $script_dir;
|
||||
my $xcat_src = "$repo_root/../xcat-core";
|
||||
my $output_root = '';
|
||||
my $apt_dir = '';
|
||||
my $manifest = '';
|
||||
@@ -108,17 +107,13 @@ my $genesis_release_checksums; # its verified SHA256SUMS, read once at sta
|
||||
# They are Architecture:all and identical for all suites, so a per-suite copy would multiply hundreds
|
||||
# of megabytes by the number of codenames for no gain.
|
||||
my $GENESIS_POOL_RELATIVE = 'pool/main/xcat-genesis-openembedded';
|
||||
my @genesis_debs; # native xcat-genesis-base-<arch> deb(s): path or URL (preferred)
|
||||
my $genesis_rpm = ''; # fallback: native-arch genesis rpm to convert
|
||||
my $genesis_rpm_ppc = ''; # fallback: cross-arch ppc genesis rpm to convert (amd64 host)
|
||||
my $require_ppc_genesis = 0;
|
||||
# File-scoped exclusive run-lock handle. MUST be file-scoped (not a lexical inside a block) so the
|
||||
# flock lives for the WHOLE process -- a lexical would close the FH and release the lock early.
|
||||
my @genesis_debs; # native xcat-genesis-base-<arch> deb(s): path or URL
|
||||
# Seconds to wait for a concurrent publisher before giving up (--publish-lock-wait). Long by default:
|
||||
# the other holder is a real publish (assemble + gate + swap), and waiting it out is almost always
|
||||
# better than failing the run.
|
||||
my $PUBLISH_LOCK_WAIT = 1800;
|
||||
my $RUN_LOCK_FH;
|
||||
# The per-arch run lock, held until the END block releases it.
|
||||
my @RUN_LOCKS;
|
||||
|
||||
# Builder map: manifest binary-package name -> the in-tree package dir that carries <dir>/sbuild.pl
|
||||
# and the maintained debian/. (goconserver's dir == its binary name.)
|
||||
@@ -130,13 +125,13 @@ my %PKG_DIR = (
|
||||
'grub2-xcat' => 'grub2-xcat',
|
||||
'elilo-xcat' => 'elilo',
|
||||
'xnba-undi' => 'xnba',
|
||||
'ipxe-xcat' => 'ipxe-xcat',
|
||||
);
|
||||
|
||||
# Build the GetOptions map from the shared standard_options() spec (so the flag vocabulary matches
|
||||
# mockbuild-all.pl), plus the apt/sbuild-specific options this orchestrator adds.
|
||||
my %DEST = (
|
||||
'repo-root' => \$repo_root,
|
||||
'xcat-source' => \$xcat_src,
|
||||
'output' => \$output_root, # alias of --output-root
|
||||
'output-root' => \$output_root,
|
||||
'manifest' => \$manifest,
|
||||
@@ -161,9 +156,6 @@ my %DEST = (
|
||||
'gpg-key-id' => \$gpg_key_id,
|
||||
'genesis-release' => \$genesis_release,
|
||||
'genesis-deb' => \@genesis_debs,
|
||||
'genesis-rpm' => \$genesis_rpm,
|
||||
'genesis-rpm-ppc' => \$genesis_rpm_ppc,
|
||||
'require-ppc-genesis' => \$require_ppc_genesis,
|
||||
);
|
||||
my %spec; # option-spec-string => destination ref
|
||||
for my $s (standard_options()) {
|
||||
@@ -180,9 +172,6 @@ $spec{'gpg-key-id=s'} = \$gpg_key_id;
|
||||
$spec{'parallel-targets=i'} = \$parallel_targets;
|
||||
$spec{'build-timeout=i'} = \$build_timeout; # per-package wall-clock bound (0 = unbounded)
|
||||
$spec{'genesis-deb=s'} = \@genesis_debs;
|
||||
$spec{'genesis-rpm=s'} = \$genesis_rpm;
|
||||
$spec{'genesis-rpm-ppc=s'} = \$genesis_rpm_ppc;
|
||||
$spec{'require-ppc-genesis!'} = \$require_ppc_genesis;
|
||||
$spec{'install-deps!'} = \$install_deps; # make this host able to run at all, then exit
|
||||
$spec{'publish!'} = \$publish; # run the finalization (assemble+sign+gate+tarball)
|
||||
$spec{'publish-lock-wait=i'} = \$PUBLISH_LOCK_WAIT; # seconds to queue behind another publisher
|
||||
@@ -195,6 +184,9 @@ $spec{'help|h'} = sub { pod2usage(-verbose => 1, -exitval => 0);
|
||||
$spec{'man'} = sub { pod2usage(-verbose => 2, -exitval => 0); };
|
||||
|
||||
GetOptions(%spec) or pod2usage(-verbose => 1, -exitval => 2);
|
||||
# A signal exits through END, so the publish lock is released. The build phase installs its own
|
||||
# forwarding handlers for the duration of the build.
|
||||
$SIG{$_} = sub { exit 1 } for qw(INT TERM HUP);
|
||||
|
||||
# --install-deps: make THIS host able to run the script, then exit. It comes first because
|
||||
# everything below assumes the toolchain is present, and a host that lacks it would fail with a
|
||||
@@ -221,7 +213,6 @@ if ($install_deps) {
|
||||
# Configuration
|
||||
# ---------------------------------------------------------------------------------------------------
|
||||
$repo_root = abs_path($repo_root);
|
||||
$xcat_src = abs_path($xcat_src) if -d $xcat_src;
|
||||
$manifest ||= "$repo_root/debs-manifest.conf";
|
||||
$arch ||= `dpkg --print-architecture 2>/dev/null`; chomp $arch;
|
||||
$arch ||= 'amd64';
|
||||
@@ -348,18 +339,20 @@ for my $cn (@dist_list) {
|
||||
my $staging = "$output_root/staging";
|
||||
unless ($dry_run) { make_path($staging); }
|
||||
|
||||
# Fail-fast PER-ARCH run lock. Within ONE pipeline run the amd64 and ppc64el stages run CONCURRENTLY
|
||||
# on their own hosts against the SAME --output-root (different arch subdirs), so a single shared lock
|
||||
# would wrongly serialize them (or deadlock). Lock per-arch instead: <output_root>/.sbuild-all.<arch>.lock
|
||||
# is only ever contended by same-arch stages, which all run on the SAME host -- so a plain local flock
|
||||
# is authoritative (no cross-host NFS lockd needed). This still blocks a SECOND run's same-arch stage
|
||||
# (cron vs manual) from racing on this arch's staging + the shared apt tree. Not taken under --dry-run.
|
||||
# PER-ARCH run locks. The amd64 and ppc64el stages of one run build concurrently on their own hosts
|
||||
# against one --output-root, so the lock is per arch: a second run of the same arch stops. A publish
|
||||
# reads the staging of every expected arch, so it also waits for the run lock of each one. The locks
|
||||
# are taken in name order. flock on the shared tree fails with ENOTSUPP through the NFS re-export, so
|
||||
# these are XCAT::NFSLock. Not taken under --dry-run.
|
||||
unless ($dry_run) {
|
||||
make_path($output_root);
|
||||
my $lockfile = "$output_root/.sbuild-all.$arch.lock";
|
||||
open($RUN_LOCK_FH, '>', $lockfile) or die "FATAL: cannot open run lock $lockfile: $!\n";
|
||||
unless (flock($RUN_LOCK_FH, LOCK_EX | LOCK_NB)) {
|
||||
die "FATAL: another sbuild-all ($arch) is already running (lock held): $lockfile\n";
|
||||
my %lock_arch = ($arch => 1);
|
||||
if ($publish) { $lock_arch{$_} = 1 for @{ resolve_expect_arches('publish', $apt_dir) }; }
|
||||
for my $a (sort keys %lock_arch) {
|
||||
# A build of this arch fails fast. A publish queues behind the builders it reads from.
|
||||
my $wait = ($publish && !($a eq $arch && !$skip_build)) ? $PUBLISH_LOCK_WAIT : 0;
|
||||
push(@RUN_LOCKS, XCAT::NFSLock->acquire("$output_root/.sbuild-all.$a.nfslock",
|
||||
timeout => $wait, label => "sbuild-all ($a) run lock"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,7 +362,6 @@ $ENV{XCAT_DEP_BUILD_TIMEOUT} = $build_timeout if defined $build_timeout;
|
||||
|
||||
print_step('Configuration');
|
||||
print " repo-root: $repo_root\n";
|
||||
print " xcat-source: $xcat_src\n";
|
||||
print " arch: $arch\n";
|
||||
print " dists: @dist_list\n";
|
||||
print " manifest: $manifest\n";
|
||||
@@ -569,7 +561,7 @@ sub build_one_codename {
|
||||
for my $pkg (@pkgs) {
|
||||
my $dir = $PKG_DIR{$pkg}
|
||||
or die "FATAL: no builder dir mapped for manifest package '$pkg'\n";
|
||||
# arch:all single-producer packages (grub2-xcat/syslinux-xcat/elilo-xcat/xnba-undi) are built
|
||||
# arch:all single-producer packages (grub2-xcat/syslinux-xcat/elilo-xcat/xnba-undi/ipxe-xcat) are built
|
||||
# ONCE on amd64 -- their source is x86-only (syslinux compiles with nasm/gcc-multilib) -- and,
|
||||
# being Architecture:all, are assembled into every arch's Packages index. They stay REQUIRED in
|
||||
# the ppc64el manifest so the gate verifies the ppc repo actually carries them, but are NOT
|
||||
@@ -677,49 +669,13 @@ sub build_deps {
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------------------------------
|
||||
# Phase: genesis-base deb (concern #2: preserve maintained packaging; native ingest preferred)
|
||||
# Phase: genesis-base deb (built natively by xcat-core, one image per Ubuntu codename)
|
||||
#
|
||||
# The Genesis image carries the kernel of the root that built it. xcat-core's builddebs.pl --genesis
|
||||
# builds one deb per codename inside that codename's chroot; this phase only ingests them and stages
|
||||
# each one into the suite it belongs to. The rpm->deb conversion that came before it gave every
|
||||
# Ubuntu release the EL kernel, so it is gone.
|
||||
# ---------------------------------------------------------------------------------------------------
|
||||
# maintained_genesis_control($arch): the maintained xCAT-genesis-builder/debian/control text, with the
|
||||
# arch-specific package/relationship names remapped to $arch (the tree carries the amd64 control).
|
||||
sub maintained_genesis_control {
|
||||
my ($a) = @_;
|
||||
my $f = "$xcat_src/xCAT-genesis-builder/debian/control";
|
||||
return undef unless -f $f;
|
||||
local $/; open my $fh, '<', $f or return undef; my $t = <$fh>; close $fh;
|
||||
# The tree carries the amd64 control; any other arch is the same text with the arch renamed.
|
||||
$t =~ s/amd64/$a/g if $a ne 'amd64';
|
||||
return $t;
|
||||
}
|
||||
# convert_genesis_rpm($rpm, $pkgname, $arch, $outdir): rpm2cpio-extract the noarch genesis rpm and
|
||||
# repackage as a .deb whose DEBIAN/control PRESERVES the maintained Depends/Breaks/Replaces and whose
|
||||
# maintainer scripts (postinst/prerm/preinst/postrm) are copied from the maintained debian/ -- so the
|
||||
# converted deb keeps the install/upgrade semantics the bare 5-field shim dropped (concern #2).
|
||||
sub convert_genesis_rpm {
|
||||
my ($rpm, $pkgname, $a, $outdir) = @_;
|
||||
my $work = tempdir(CLEANUP => 1);
|
||||
my $get = ($rpm =~ m{^https?://})
|
||||
? "curl -fsSL " . sh_quote($rpm) . " | rpm2cpio"
|
||||
: "rpm2cpio " . sh_quote($rpm);
|
||||
run("cd $work && $get | cpio -idm --quiet");
|
||||
my $ver = `rpm -qp --qf '%{VERSION}-%{RELEASE}' ${\ sh_quote($rpm)} 2>/dev/null`; chomp $ver;
|
||||
$ver ||= "2.18.0-snap$snap_ts";
|
||||
$ver =~ s/\.(el|fc)\d+.*$//; # drop the EL dist tag from the rpm Release
|
||||
my $pkgd = "$work/pkg"; make_path("$pkgd/DEBIAN", "$pkgd/opt/xcat");
|
||||
run("cp -a $work/opt/xcat/. $pkgd/opt/xcat/ 2>/dev/null || true", nofail => 1);
|
||||
my $control = genesis_deb_control(maintained_genesis_control($a), $pkgname, $ver, 'all');
|
||||
if (!$dry_run) {
|
||||
open my $fh, '>', "$pkgd/DEBIAN/control" or die "write control: $!\n"; print $fh $control; close $fh;
|
||||
# preserve maintainer scripts from the maintained packaging (install/upgrade behavior)
|
||||
my $mdeb = "$xcat_src/xCAT-genesis-builder/debian";
|
||||
for my $s (qw(postinst preinst postrm prerm)) {
|
||||
next unless -f "$mdeb/$s";
|
||||
copy("$mdeb/$s", "$pkgd/DEBIAN/$s"); chmod 0755, "$pkgd/DEBIAN/$s";
|
||||
}
|
||||
}
|
||||
make_path($outdir);
|
||||
run("dpkg-deb --build " . sh_quote($pkgd) . " " . sh_quote("$outdir/${pkgname}_${ver}_all.deb"));
|
||||
return "$outdir/${pkgname}_${ver}_all.deb";
|
||||
}
|
||||
# genesis_in_manifest(): whether the legacy Genesis deb belongs to this run at all. It is named
|
||||
# per target in the manifest, and riscv64 does not name it: its Genesis is the OpenEmbedded package
|
||||
# published once into the shared pool. Without this, a plain --arch riscv64 run reaches
|
||||
@@ -747,35 +703,12 @@ sub build_genesis {
|
||||
print " ingested native genesis deb: $base\n";
|
||||
$produced_native = 1 if $base =~ /^\Q$native_arch_pkg\E_/;
|
||||
}
|
||||
# 2) else convert the native-arch rpm (metadata-preserving)
|
||||
if (!$produced_native) {
|
||||
if ($genesis_rpm) {
|
||||
print " converting native-arch genesis rpm -> deb (preserving control + scripts)\n";
|
||||
convert_genesis_rpm($genesis_rpm, $native_arch_pkg, $arch, $gen);
|
||||
} elsif (!@genesis_debs) {
|
||||
die "FATAL: no native genesis for $arch: pass --genesis-deb (preferred) or --genesis-rpm\n";
|
||||
}
|
||||
}
|
||||
# 3) cross-arch ppc genesis on the amd64 host (#7610): convert the ppc rpm if given
|
||||
if ($arch eq 'amd64') {
|
||||
my $have_ppc = grep { basename($_) =~ /^xcat-genesis-base-ppc64el_/ } glob("$gen/*.deb");
|
||||
if (!$have_ppc && $genesis_rpm_ppc) {
|
||||
print " converting cross-arch ppc64el genesis rpm -> deb (#7610)\n";
|
||||
convert_genesis_rpm($genesis_rpm_ppc, 'xcat-genesis-base-ppc64el', 'ppc64el', $gen);
|
||||
$have_ppc = 1;
|
||||
}
|
||||
if (!$have_ppc) {
|
||||
my $msg = "no ppc64el genesis (pass --genesis-deb/--genesis-rpm-ppc): an amd64 MN cannot "
|
||||
. "netboot ppc nodes (#7610)";
|
||||
die "FATAL: $msg\n" if $require_ppc_genesis;
|
||||
warn "WARN: $msg\n";
|
||||
}
|
||||
}
|
||||
# stage the arch:all genesis deb(s) into every codename (this host's arch subdir; the cross-arch
|
||||
# ppc genesis produced on the amd64 host rides in the amd64 subdir and is picked up by assemble).
|
||||
# Use BuildUtils::cross_copy_genesis_deb -- the tested, hash-based, stale-dropping copier -- once
|
||||
# per genesis package-arch present in $gen (the native-arch one, plus the cross-converted ppc64el
|
||||
# one on the amd64 host). It refreshes a stale same-name deb by content and is idempotent.
|
||||
die "FATAL: no Genesis deb for $arch: pass --genesis-deb.\n"
|
||||
. " xcat-core builds them with `builddebs.pl --genesis-only --genesis-dist <codename>`.\n"
|
||||
unless $produced_native || $dry_run;
|
||||
# Stage each suite's OWN image. cross_copy_genesis_deb is the tested, hash-based, stale-dropping
|
||||
# copier; the codename narrows it to the deb built on that release. A deb with no codename in its
|
||||
# version serves every suite, which is how a package published before the native build is reused.
|
||||
my %gen_arches;
|
||||
for my $d (glob("$gen/*.deb")) {
|
||||
$gen_arches{$1}++ if basename($d) =~ /^xcat-genesis-base-([a-z0-9]+)_/;
|
||||
@@ -788,7 +721,10 @@ sub build_genesis {
|
||||
}
|
||||
make_path($dst);
|
||||
for my $ga (sort keys %gen_arches) {
|
||||
my $n = cross_copy_genesis_deb($gen, $dst, $ga, undef);
|
||||
my $n = cross_copy_genesis_deb($gen, $dst, $ga, undef, $cn);
|
||||
die "FATAL: no xcat-genesis-base-$ga image built for $cn: xcat-core builds one per\n"
|
||||
. " codename, so --genesis-dist must name every release this run publishes.\n"
|
||||
unless $n || glob("$dst/xcat-genesis-base-$ga\_*.deb");
|
||||
print " staged xcat-genesis-base-$ga -> $cn/$arch ($n newly copied)\n";
|
||||
}
|
||||
}
|
||||
@@ -1079,29 +1015,26 @@ sub verify_assembled_repo {
|
||||
# complete repo or the new complete repo -- never a half-wiped pool or an index that does not
|
||||
# match its Release. A failed gate leaves the published tree untouched.
|
||||
#
|
||||
# The lock file lives on the shared tree; within a host flock() is authoritative, which is what
|
||||
# matters, since the pipeline's finalization step always runs on one host (the amd64 Ubuntu builder).
|
||||
# Publishers of one tree can run on different hosts, and flock through an NFS re-export does not
|
||||
# exclude them, so the publish lock is an XCAT::NFSLock. The END block releases it.
|
||||
# ---------------------------------------------------------------------------------------------------
|
||||
my $PUBLISH_LOCK_FH;
|
||||
my $PUBLISH_LOCK;
|
||||
|
||||
sub acquire_publish_lock {
|
||||
make_path($output_root);
|
||||
my $lockfile = "$output_root/.sbuild-all.publish.lock";
|
||||
open($PUBLISH_LOCK_FH, '>', $lockfile) or die "FATAL: cannot open publish lock $lockfile: $!\n";
|
||||
unless (flock($PUBLISH_LOCK_FH, LOCK_EX | LOCK_NB)) {
|
||||
print " publish lock is held by another run -- waiting up to ${PUBLISH_LOCK_WAIT}s: $lockfile\n";
|
||||
local $SIG{ALRM} = sub {
|
||||
die "FATAL: timed out after ${PUBLISH_LOCK_WAIT}s waiting for the publish lock $lockfile\n";
|
||||
};
|
||||
alarm($PUBLISH_LOCK_WAIT);
|
||||
my $ok = flock($PUBLISH_LOCK_FH, LOCK_EX);
|
||||
alarm(0);
|
||||
die "FATAL: cannot take the publish lock $lockfile: $!\n" unless $ok;
|
||||
}
|
||||
my $lockfile = "$output_root/.sbuild-all.publish.nfslock";
|
||||
print " taking the publish lock, waiting up to ${PUBLISH_LOCK_WAIT}s: $lockfile\n";
|
||||
$PUBLISH_LOCK = XCAT::NFSLock->acquire($lockfile,
|
||||
timeout => $PUBLISH_LOCK_WAIT, label => 'publish lock');
|
||||
print " publish lock acquired: $lockfile\n";
|
||||
return $lockfile;
|
||||
}
|
||||
|
||||
END {
|
||||
$PUBLISH_LOCK->release if $PUBLISH_LOCK;
|
||||
$_->release for reverse(@RUN_LOCKS);
|
||||
}
|
||||
|
||||
# assemble_into($dir, $expect_arches): (re)assemble every --dists codename inside $dir from the
|
||||
# validated staging tree, index it per expected binary-<arch>, write + sign Release. $dir is the SIDE
|
||||
# tree, never the published one.
|
||||
@@ -1307,7 +1240,7 @@ sub publish_repo {
|
||||
print_step('Publish apt repo (locked, assembled aside, swapped in atomically)');
|
||||
my $expect = resolve_expect_arches('publish', $apt_dir);
|
||||
if ($dry_run) {
|
||||
print " [dry-run] would lock $output_root/.sbuild-all.publish.lock, assemble @dist_list for "
|
||||
print " [dry-run] would lock $output_root/.sbuild-all.publish.nfslock, assemble @dist_list for "
|
||||
. join(' ', @$expect) . " into $apt_dir.publish-<run-id>.<pid>, gate it, then rename it "
|
||||
. "onto $apt_dir\n";
|
||||
return;
|
||||
@@ -1391,7 +1324,10 @@ sbuild-all.pl - build, validate, sign and assemble the xcat-dep Ubuntu/Debian ap
|
||||
|
||||
# STEP 1 -- per arch, on that arch's build host: build + validate into staging (does NOT publish):
|
||||
sbuild-all.pl --arch amd64 --dists "focal jammy noble resolute" \
|
||||
--xcat-source ../xcat-core --genesis-rpm <xCAT-genesis-base rpm>
|
||||
--genesis-deb <xcat-genesis-base-amd64_..~focal_all.deb> \
|
||||
--genesis-deb <xcat-genesis-base-amd64_..~jammy_all.deb> \
|
||||
--genesis-deb <xcat-genesis-base-amd64_..~noble_all.deb> \
|
||||
--genesis-deb <xcat-genesis-base-amd64_..~resolute_all.deb>
|
||||
sbuild-all.pl --arch ppc64el --dists "focal jammy noble resolute" --skip-genesis
|
||||
sbuild-all.pl --arch riscv64 --dists "focal jammy noble resolute"
|
||||
|
||||
@@ -1404,7 +1340,7 @@ sbuild-all.pl - build, validate, sign and assemble the xcat-dep Ubuntu/Debian ap
|
||||
sbuild-all.pl --target noble-amd64 ... # equivalent single-target form
|
||||
|
||||
# single host, build AND publish in one go (add --publish explicitly):
|
||||
sbuild-all.pl --arch amd64 --dists noble --genesis-rpm <rpm> --publish --expect-arch amd64 \
|
||||
sbuild-all.pl --arch amd64 --dists noble --genesis-deb <deb> --publish --expect-arch amd64 \
|
||||
--gpg-sign --gpg-key-id <id> --gpg-home <dir>
|
||||
|
||||
# verify an already-published tree out of band (signatures checked by DEFAULT):
|
||||
@@ -1472,10 +1408,11 @@ failure (see L</"Each package builds in a clean, disposable chroot">).
|
||||
|
||||
=item Genesis
|
||||
|
||||
Produces the C<xcat-genesis-base> deb: a native deb is ingested as-is when provided
|
||||
(C<--genesis-deb>); otherwise the rpm is converted while B<preserving the maintained control>
|
||||
(Depends/Breaks/Replaces) and maintainer scripts. The amd64 host also converts the cross-arch
|
||||
ppc64el genesis (issue #7610) unless C<--require-ppc-genesis> gates it. Skipped with C<--skip-genesis>.
|
||||
Ingests the C<xcat-genesis-base> debs named by C<--genesis-deb> and stages each one into the
|
||||
suite it was built for. The Genesis image carries the kernel of the root that built it, so
|
||||
xcat-core builds one deb per Ubuntu codename (C<builddebs.pl --genesis-only --genesis-dist
|
||||
E<lt>codenameE<gt>>) and stamps the codename into the version. A run that publishes a codename
|
||||
with no image for it stops. Skipped with C<--skip-genesis>.
|
||||
An B<OpenEmbedded Genesis package release> is a separate, verified input published by
|
||||
C<--genesis-release>; it is not built here.
|
||||
|
||||
@@ -1542,9 +1479,9 @@ Build a single target; the arch must match C<--arch>.
|
||||
|
||||
Per-target manifest. Default: C<< <repo-root>/debs-manifest.conf >>.
|
||||
|
||||
=item B<--repo-root> / B<--xcat-source> C<path>
|
||||
=item B<--repo-root> C<path>
|
||||
|
||||
xcat-dep root (default: the script's dir) / xcat-core root (for the maintained genesis packaging).
|
||||
xcat-dep root (default: the script's dir).
|
||||
|
||||
=item B<--output-root> / B<--apt-dir> C<path>
|
||||
|
||||
@@ -1558,15 +1495,8 @@ build hosts).
|
||||
|
||||
=item B<--genesis-deb> C<path|url>
|
||||
|
||||
Native C<xcat-genesis-base> deb to ingest (repeatable; preferred over conversion).
|
||||
|
||||
=item B<--genesis-rpm> / B<--genesis-rpm-ppc> C<path|url>
|
||||
|
||||
Native-arch genesis rpm to convert / cross-arch ppc genesis rpm to convert on amd64 (issue #7610).
|
||||
|
||||
=item B<--require-ppc-genesis>
|
||||
|
||||
Make a missing ppc64el genesis fatal (default: warn).
|
||||
An C<xcat-genesis-base> deb that xcat-core built. Repeatable: pass one per codename, and one per
|
||||
architecture on a host that stages another architecture's image.
|
||||
|
||||
=item B<--genesis-release> C<dir>
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env perl
|
||||
# The Genesis image carries the kernel of the release that built it, so xcat-core builds one deb
|
||||
# per Ubuntu codename and stamps the codename into the version. Staging all of them into every
|
||||
# suite publishes three images per suite and lets apt pick the newest, which is the image of
|
||||
# another release.
|
||||
#
|
||||
# sbuild-all.pl also kept an rpm->deb fallback, the EL image converted with rpm2cpio, which gave
|
||||
# an Ubuntu node an image built from an EL kernel. It is removed.
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Basename qw(basename);
|
||||
use File::Path qw(make_path);
|
||||
use File::Spec;
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use lib $FindBin::Bin . '/..';
|
||||
use Test::More;
|
||||
|
||||
require BuildUtils;
|
||||
|
||||
my $root = File::Spec->rel2abs("$FindBin::Bin/..");
|
||||
|
||||
ok(BuildUtils->can('genesis_debs_for_codename'),
|
||||
'BuildUtils selects the Genesis deb of one codename');
|
||||
|
||||
unless (BuildUtils->can('genesis_debs_for_codename')) {
|
||||
diag('sbuild-all.pl stages every Genesis deb into every suite');
|
||||
done_testing();
|
||||
exit;
|
||||
}
|
||||
|
||||
my @built = map { "/staging/$_" } qw(
|
||||
xcat-genesis-base-amd64_2.19.0-snap202609121200~jammy_all.deb
|
||||
xcat-genesis-base-amd64_2.19.0-snap202609121200~noble_all.deb
|
||||
xcat-genesis-base-amd64_2.19.0-snap202609121200~resolute_all.deb
|
||||
);
|
||||
|
||||
is_deeply([ BuildUtils::genesis_debs_for_codename(\@built, 'noble') ],
|
||||
[ '/staging/xcat-genesis-base-amd64_2.19.0-snap202609121200~noble_all.deb' ],
|
||||
'noble takes the image built on noble');
|
||||
is_deeply([ BuildUtils::genesis_debs_for_codename(\@built, 'jammy') ],
|
||||
[ '/staging/xcat-genesis-base-amd64_2.19.0-snap202609121200~jammy_all.deb' ],
|
||||
'jammy takes the image built on jammy');
|
||||
is_deeply([ BuildUtils::genesis_debs_for_codename(\@built, 'focal') ], [],
|
||||
'a release with no image of its own takes none');
|
||||
|
||||
my @unmarked = ('/staging/xcat-genesis-base-amd64_2.19.0-snap202609121200_all.deb');
|
||||
is_deeply([ BuildUtils::genesis_debs_for_codename(\@unmarked, 'noble') ], \@unmarked,
|
||||
'a deb built for no particular release serves every release');
|
||||
is_deeply([ BuildUtils::genesis_debs_for_codename(\@built, undef) ], \@built,
|
||||
'with no codename every deb is taken');
|
||||
is_deeply([ BuildUtils::genesis_debs_for_codename([], 'noble') ], [],
|
||||
'no deb is no deb');
|
||||
|
||||
# cross_copy_genesis_deb stages into one suite, so it must take the codename too.
|
||||
my $tmp = tempdir(CLEANUP => 1);
|
||||
my ($from, $to) = ("$tmp/from", "$tmp/to");
|
||||
make_path($from, $to);
|
||||
for my $deb (@built) {
|
||||
open my $fh, '>', "$from/" . basename($deb) or die $!;
|
||||
print {$fh} basename($deb);
|
||||
close $fh;
|
||||
}
|
||||
BuildUtils::cross_copy_genesis_deb($from, $to, 'amd64', undef, 'noble');
|
||||
my @staged = map { basename($_) } glob("$to/*.deb");
|
||||
is_deeply(\@staged, [ 'xcat-genesis-base-amd64_2.19.0-snap202609121200~noble_all.deb' ],
|
||||
'only the codename its own image is staged into a suite');
|
||||
|
||||
# The rpm fallback is gone: the option it hangs on is not accepted any more.
|
||||
for my $option (qw(--genesis-rpm --genesis-rpm-ppc --require-ppc-genesis)) {
|
||||
my $out = qx{cd '$root' && perl ./sbuild-all.pl $option x --dry-run 2>&1};
|
||||
isnt($? >> 8, 0, "sbuild-all.pl rejects $option");
|
||||
like($out, qr/Unknown option/i, "$option is not an option any more");
|
||||
}
|
||||
|
||||
done_testing();
|
||||
@@ -4,7 +4,7 @@ use warnings;
|
||||
use Cwd qw(abs_path);
|
||||
use File::Basename qw(basename);
|
||||
use File::Copy qw(copy);
|
||||
use File::Path qw(make_path);
|
||||
use File::Path qw(make_path remove_tree);
|
||||
use Fcntl qw(:flock);
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
@@ -23,6 +23,7 @@ use XCAT::BuildUtils qw(
|
||||
read_binary
|
||||
write_binary
|
||||
);
|
||||
use XCAT::NFSLock ();
|
||||
use XCAT::GenesisRelease qw(
|
||||
architectures
|
||||
deb_package_name
|
||||
@@ -73,6 +74,7 @@ SKIP: {
|
||||
test_skip_build_collects_results();
|
||||
test_dry_run_release();
|
||||
test_rpm_repository_lock();
|
||||
test_finalize_cell_lock();
|
||||
test_rpm_signal_cleanup();
|
||||
}
|
||||
|
||||
@@ -169,6 +171,9 @@ sub test_rpm_consumer {
|
||||
push(@perl_lib, $ENV{PERL5LIB})
|
||||
if defined($ENV{PERL5LIB}) && $ENV{PERL5LIB} ne '';
|
||||
local $ENV{PERL5LIB} = join(':', @perl_lib);
|
||||
# An interrupted publication left a staging tree. A real run recovers it.
|
||||
my $abandoned = "$output/xcat-dep/.common.abandoned";
|
||||
make_path($abandoned);
|
||||
my $log = "$tmp/rpm-consumer.log";
|
||||
my $status = run_capture(
|
||||
$log,
|
||||
@@ -186,6 +191,7 @@ sub test_rpm_consumer {
|
||||
);
|
||||
|
||||
is($status, 0, 'RPM repository accepts a verified Genesis release');
|
||||
ok(!-e $abandoned, 'the run removes the staging tree of an interrupted publication');
|
||||
is(
|
||||
sprintf('%04o', (stat($common_repo))[2] & 0x0fff),
|
||||
'0755',
|
||||
@@ -938,11 +944,16 @@ sub test_dry_run_release {
|
||||
sub test_rpm_repository_lock {
|
||||
my $output = "$tmp/rpm-lock-output";
|
||||
my $repository = "$tmp/rpm-shared-repository";
|
||||
my $target = 'test+epel-10-' . capture_command('uname', '-m');
|
||||
my $arch = capture_command('uname', '-m');
|
||||
my $target = "test+epel-10-$arch";
|
||||
my $scratch_repo_root = "$tmp/rpm-lock-repo-root";
|
||||
write_target_manifest($scratch_repo_root, $target);
|
||||
make_path("$repository/.lock");
|
||||
write_binary("$repository/.lock/owner", "host=other\npid=1\nepoch=1\n");
|
||||
# The cell this target deploys is locked by a run on another machine.
|
||||
my $cell_lock = "$repository/rh10/.$arch.lock";
|
||||
make_path($cell_lock);
|
||||
write_binary("$cell_lock/metadata",
|
||||
XCAT::NFSLock::format_metadata({ 'machine-id' => 'another-machine', 'boot-id' => 'b',
|
||||
pid => 1, pstart => 1, token => 't' }));
|
||||
|
||||
my @perl_lib;
|
||||
push(@perl_lib, write_forkmanager_stub("$tmp/perl-lock-stub"))
|
||||
@@ -963,9 +974,11 @@ sub test_rpm_repository_lock {
|
||||
'--no-verify-repo',
|
||||
'--skip-createrepo', '--skip-tarball', '--dry-run',
|
||||
);
|
||||
isnt($status, 0, 'a shared RPM repository cannot have two publishers');
|
||||
like(read_binary($log), qr/repository \Q$repository\E is locked/,
|
||||
'the lock failure names the shared repository');
|
||||
isnt($status, 0, 'a repository cell cannot have two publishers');
|
||||
like(read_binary($log), qr/^Trying to unlock \Q$cell_lock\E failed after 1 retry;/m,
|
||||
'the lock failure names the locked cell');
|
||||
ok(-d $cell_lock, 'a lock held on another machine is left in place');
|
||||
remove_tree($cell_lock);
|
||||
|
||||
my $backup = "$repository/.common.previous.999";
|
||||
my $staging = "$repository/.common.abandoned";
|
||||
@@ -981,12 +994,54 @@ sub test_rpm_repository_lock {
|
||||
'--target', $target,
|
||||
'--skip-build', '--skip-genesis', '--skip-xcat-dep', '--skip-perl',
|
||||
'--no-verify-repo',
|
||||
'--skip-createrepo', '--skip-tarball', '--dry-run', '--force-unlock',
|
||||
'--skip-createrepo', '--skip-tarball', '--dry-run',
|
||||
);
|
||||
is($forced_status, 0, '--force-unlock recovers an interrupted RPM publication');
|
||||
ok(-f "$repository/common/marker",
|
||||
'the interrupted common repository is restored before publication');
|
||||
ok(!-d $staging, 'abandoned common repository staging is removed');
|
||||
is($forced_status, 0, 'a dry run starts beside an interrupted RPM publication')
|
||||
or diag(read_binary($forced_log));
|
||||
ok(-f "$backup/marker", 'a dry run leaves the moved-aside common repository in place');
|
||||
ok(-d $staging, 'a dry run leaves the abandoned staging tree in place');
|
||||
ok(!-e "$repository/common", 'a dry run does not restore the common repository');
|
||||
}
|
||||
|
||||
sub test_finalize_cell_lock {
|
||||
my $x86 = "$tmp/finalize-x86";
|
||||
my $ppc = "$tmp/finalize-ppc";
|
||||
make_path("$x86/rh10/x86_64", "$ppc/rh10/ppc64le");
|
||||
# A build on another machine is still deploying the x86_64 cell.
|
||||
my $cell_lock = "$x86/rh10/.x86_64.lock";
|
||||
make_path($cell_lock);
|
||||
write_binary("$cell_lock/metadata",
|
||||
XCAT::NFSLock::format_metadata({ 'machine-id' => 'another-machine', 'boot-id' => 'b',
|
||||
pid => 1, pstart => 1, token => 't' }));
|
||||
|
||||
my $log = "$tmp/finalize-lock.log";
|
||||
my $status = run_capture(
|
||||
$log,
|
||||
$^X, $rpm_consumer,
|
||||
'--repo-root', $repo_root,
|
||||
'--finalize-xcat-dep', '--x86_64-repo', $x86, '--ppc64le-repo', $ppc,
|
||||
);
|
||||
isnt($status, 0, 'finalize does not rewrite a cell that a build holds');
|
||||
like(read_binary($log), qr/^Trying to unlock \Q$cell_lock\E failed after 1 retry;/m,
|
||||
'finalize names the cell lock it waited for');
|
||||
ok(-d $cell_lock, 'the build keeps its cell lock');
|
||||
ok(!-e "$ppc/rh10/.ppc64le.lock", 'finalize releases the cell locks it took');
|
||||
|
||||
# Finalize of the ppc64le cells takes only their locks: the x86_64 cell lock of another machine
|
||||
# does not stop it. It stops at the next check, the missing genesis rpm.
|
||||
my $arch_log = "$tmp/finalize-arch.log";
|
||||
my $arch_status = run_capture(
|
||||
$arch_log,
|
||||
$^X, $rpm_consumer,
|
||||
'--repo-root', $repo_root,
|
||||
'--finalize-xcat-dep', '--finalize-arch', 'ppc64le',
|
||||
'--x86_64-repo', $x86, '--ppc64le-repo', $ppc,
|
||||
);
|
||||
isnt($arch_status, 0, 'finalize of an empty ppc64le cell fails');
|
||||
unlike(read_binary($arch_log), qr/^Trying to unlock/m, 'finalize of ppc64le cells takes no x86_64 cell lock');
|
||||
like(read_binary($arch_log), qr/no x86_64 xCAT-genesis-base rpm/, 'finalize of ppc64le cells reaches the genesis check');
|
||||
ok(-d $cell_lock, 'the x86_64 cell lock of the other machine is left in place');
|
||||
ok(!-e "$ppc/rh10/.ppc64le.lock", 'finalize releases the ppc64le cell lock');
|
||||
}
|
||||
|
||||
sub test_rpm_signal_cleanup {
|
||||
@@ -995,13 +1050,10 @@ sub test_rpm_signal_cleanup {
|
||||
my $signal_bin = "$tmp/rpm-signal-bin";
|
||||
my $log = "$tmp/rpm-signal.log";
|
||||
make_path($repository, $signal_bin);
|
||||
write_binary(
|
||||
"$signal_bin/uname",
|
||||
"#!/bin/sh\n"
|
||||
. "if [ \"\$1\" = -m ]; then sleep 60; exit 1; fi\n"
|
||||
. "exec /usr/bin/uname \"\$@\"\n",
|
||||
);
|
||||
chmod(0755, "$signal_bin/uname") or die $!;
|
||||
# nproc runs after every lock is taken and before any build starts.
|
||||
write_binary("$signal_bin/nproc", "#!/bin/sh\nsleep 60\nexit 1\n");
|
||||
chmod(0755, "$signal_bin/nproc") or die $!;
|
||||
my $cell_lock = "$repository/rh10/." . capture_command('uname', '-m') . '.lock';
|
||||
|
||||
my $pid = fork();
|
||||
die "Cannot fork signal test: $!" unless defined($pid);
|
||||
@@ -1026,7 +1078,7 @@ sub test_rpm_signal_cleanup {
|
||||
|
||||
my $locked = 0;
|
||||
for (1 .. 200) {
|
||||
if (-d "$output/.lock" && -d "$repository/.lock") {
|
||||
if (-d "$output/.lock" && -d $cell_lock) {
|
||||
$locked = 1;
|
||||
last;
|
||||
}
|
||||
@@ -1037,7 +1089,7 @@ sub test_rpm_signal_cleanup {
|
||||
waitpid($pid, 0);
|
||||
is($? >> 8, 1, 'SIGTERM follows the publisher cleanup exit path');
|
||||
ok(!-d "$output/.lock", 'SIGTERM releases the output lock');
|
||||
ok(!-d "$repository/.lock", 'SIGTERM releases the repository lock');
|
||||
ok(!-d $cell_lock, 'SIGTERM releases the repository cell lock');
|
||||
}
|
||||
|
||||
sub test_publish_lock {
|
||||
@@ -1046,9 +1098,25 @@ sub test_publish_lock {
|
||||
stage_legacy_deb("$tmp/lock-deb", $output);
|
||||
make_path($output);
|
||||
|
||||
my $lockfile = "$output/.sbuild-all.publish.lock";
|
||||
open(my $held, '>', $lockfile) or die "Cannot create $lockfile: $!\n";
|
||||
flock($held, LOCK_EX | LOCK_NB) or die "Cannot hold $lockfile: $!\n";
|
||||
# This process is a live build run on the same host. The publish reads the staging of every
|
||||
# expected arch, so it waits for the run lock of each one. The run lock is an XCAT::NFSLock,
|
||||
# because flock on the shared tree fails with ENOTSUPP through the NFS re-export.
|
||||
for my $build_arch (qw(amd64 ppc64el)) {
|
||||
my $run_lock = "$output/.sbuild-all.$build_arch.nfslock";
|
||||
my $running = XCAT::NFSLock->acquire($run_lock);
|
||||
my $run_log = "$tmp/deb-run-locked-$build_arch.log";
|
||||
my $run_status = run_apt_consumer(log => $run_log, output => $output, apt_dir => $apt_root,
|
||||
extra => [ '--publish-lock-wait', '2' ]);
|
||||
isnt($run_status, 0, "a publish does not start while a $build_arch run holds its lock");
|
||||
like(read_binary($run_log), qr/^Trying to unlock \Q$run_lock\E failed after 1 retry;/m,
|
||||
"the refusal names the $build_arch run lock");
|
||||
ok(!-d "$apt_root/dists", "nothing is published while a $build_arch run holds its lock");
|
||||
$running->release;
|
||||
}
|
||||
|
||||
# This process is a live publisher on the same host.
|
||||
my $lockfile = "$output/.sbuild-all.publish.nfslock";
|
||||
my $held = XCAT::NFSLock->acquire($lockfile);
|
||||
|
||||
my $locked_log = "$tmp/deb-locked.log";
|
||||
my $locked_status = run_apt_consumer(
|
||||
@@ -1056,11 +1124,11 @@ sub test_publish_lock {
|
||||
extra => [ '--publish-lock-wait', '2' ],
|
||||
);
|
||||
isnt($locked_status, 0, 'a locked apt tree is not published into');
|
||||
like(read_binary($locked_log), qr/waiting for the publish lock \Q$lockfile\E/,
|
||||
like(read_binary($locked_log), qr/^Trying to unlock \Q$lockfile\E failed after 1 retry;/m,
|
||||
'the refusal names the lock another run owns');
|
||||
ok(!-d "$apt_root/dists", 'nothing is published while another run holds the lock');
|
||||
|
||||
close($held);
|
||||
$held->release;
|
||||
|
||||
# sbuild-all.pl never publishes in place: it assembles a COMPLETE side tree and renames it onto
|
||||
# the repository, so there is no half-written state to recover and no per-file backup to restore.
|
||||
@@ -1074,6 +1142,7 @@ sub test_publish_lock {
|
||||
log => $freed_log, output => $output, apt_dir => $apt_root, dists => ['noble']);
|
||||
is($freed_status, 0, 'the publish runs once the lock is released');
|
||||
ok(-f "$apt_root/dists/noble/Release", 'the released lock lets the tree be published');
|
||||
ok(!-e $lockfile, 'the publisher releases the publish lock when it exits');
|
||||
isnt(read_binary("$apt_root/dists/noble/Release"), "abandoned\n",
|
||||
'a side tree abandoned by a dead run is never published');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
#!/usr/bin/perl
|
||||
# Behaviour test for the Go toolchain the Ubuntu goconserver build uses.
|
||||
#
|
||||
# riscv64 has no build host, so its chroot runs under qemu-user. A Go toolchain built FOR riscv64
|
||||
# therefore runs emulated, and `go build` parks its threads in futex_wait and never finishes: three
|
||||
# xcat-dep-ubuntu-cd riscv64 cells burned the whole 9000s budget with no CPU ticks at all. Go
|
||||
# cross-compiles, so the toolchain must be the BUILD HOST's and the target must come from GOARCH.
|
||||
#
|
||||
# The test LIFTS the build shell out of goconserver/sbuild.pl, RUNS it, and asserts on what the run
|
||||
# asked for -- the toolchain tarball it fetched, and the environment the real debian/rules passed to
|
||||
# `go build`. It never matches the source of the thing it tests.
|
||||
#
|
||||
# Every command that could write outside the scratch tree is shadowed by a recorder that refuses the
|
||||
# write and reports it, so a build that reaches for /usr/local is a FAILED assertion here rather than
|
||||
# damage to the host running the suite.
|
||||
use strict;
|
||||
use warnings;
|
||||
use Test::More;
|
||||
use File::Temp qw(tempdir);
|
||||
use File::Path qw(make_path);
|
||||
use FindBin qw($RealBin);
|
||||
|
||||
my $pkg_dir = "$RealBin/../goconserver";
|
||||
plan skip_all => 'goconserver/sbuild.pl not found' unless -f "$pkg_dir/sbuild.pl";
|
||||
plan skip_all => 'bash is not available' unless -x '/bin/bash';
|
||||
|
||||
my $LIFT = 'lifted by t/goconserver_cross_build.t';
|
||||
|
||||
# The build shell, produced by the real builder code. The whole marked region is evaluated, so the
|
||||
# architecture the builder stamps into the script comes from the builder rather than from this test.
|
||||
# die (never BAIL_OUT) when the lift stops matching: prove stops the WHOLE suite on a bail-out, and a
|
||||
# silent miss would leave this file covering nothing.
|
||||
sub build_script {
|
||||
open(my $fh, '<', "$pkg_dir/sbuild.pl") or die "read sbuild.pl: $!";
|
||||
my $src = do { local $/; <$fh> };
|
||||
close($fh);
|
||||
my ($region) = $src =~ /^\#[^\n]*\Q$LIFT\E[^\n]*\n(.*?^BUILD$)/ms
|
||||
or die "goconserver/sbuild.pl no longer marks its build script with '$LIFT' -- "
|
||||
. "this test can no longer reach the code it covers\n";
|
||||
my $build = eval "$region\n\$build"; ## no critic
|
||||
die "could not evaluate the lifted build script: $@\n" if $@;
|
||||
die "the lifted build script is empty\n" unless defined $build && $build =~ /\S/;
|
||||
return $build;
|
||||
}
|
||||
|
||||
sub write_stub {
|
||||
my ($dir, $name, $body) = @_;
|
||||
open(my $fh, '>', "$dir/$name") or die "write $dir/$name: $!";
|
||||
print {$fh} "#!/bin/bash\n$body\n";
|
||||
close($fh);
|
||||
chmod(0755, "$dir/$name") or die "chmod $dir/$name: $!";
|
||||
}
|
||||
|
||||
# run_build($target_arch): run the build shell with the chroot's architecture reported as
|
||||
# $target_arch, and return what it asked the outside world to do.
|
||||
sub run_build {
|
||||
my ($target_arch) = @_;
|
||||
my $root = tempdir(CLEANUP => 1);
|
||||
my ($bin, $rec, $work) = ("$root/bin", "$root/rec", "$root/work");
|
||||
make_path($bin, $rec, $work);
|
||||
|
||||
# The build runs with CWD = a copy of the package dir, and reads ../gomod and ./debian from it.
|
||||
system('cp', '-rL', "$pkg_dir/$_", "$work/$_") == 0 or die "stage $_: $!" for qw(gomod debian);
|
||||
|
||||
# dpkg answers for the CHROOT, which is the architecture the build must produce.
|
||||
write_stub($bin, 'dpkg', qq{
|
||||
[ "\$1" = --print-architecture ] && { echo '$target_arch'; exit 0; }
|
||||
exec /usr/bin/dpkg "\$\@"
|
||||
});
|
||||
write_stub($bin, 'curl', qq{
|
||||
for a in "\$\@"; do case "\$a" in http*) echo "\$a" >> '$rec/curl-urls';; esac; done
|
||||
exit 0
|
||||
});
|
||||
# tar and rm police their target: anything outside the scratch tree is recorded, not performed.
|
||||
write_stub($bin, 'tar', qq{
|
||||
dest=''; prev=''
|
||||
for a in "\$\@"; do [ "\$prev" = -C ] && dest="\$a"; prev="\$a"; done
|
||||
case "\$dest" in '$root'/*) ;; *) echo "tar -C \$dest" >> '$rec/escapes';; esac
|
||||
exit 0
|
||||
});
|
||||
write_stub($bin, 'rm', qq{
|
||||
for a in "\$\@"; do
|
||||
case "\$a" in
|
||||
-*|'$root'/*) ;;
|
||||
/*) echo "rm \$a" >> '$rec/escapes'; exit 0;;
|
||||
esac
|
||||
done
|
||||
exec /bin/rm "\$\@"
|
||||
});
|
||||
# Only `git init <dir>` has to have an effect; the clone has no source this test needs.
|
||||
write_stub($bin, 'git', qq{
|
||||
if [ "\$1" = init ]; then shift
|
||||
for a in "\$\@"; do case "\$a" in -*) ;; *) mkdir -p "\$a";; esac; done
|
||||
fi
|
||||
exit 0
|
||||
});
|
||||
write_stub($bin, 'dch', 'exit 0');
|
||||
# The real debian/rules has to see the environment, so run its build target for real.
|
||||
write_stub($bin, 'dpkg-buildpackage', 'make -f debian/rules override_dh_auto_build');
|
||||
# The downloaded toolchain never lands, so `go` always resolves here. It records the environment
|
||||
# of each invocation, which is the thing under test.
|
||||
write_stub($bin, 'go', qq{
|
||||
echo "GOARCH=\${GOARCH-} GOOS=\${GOOS-} ARGV=\$*" >> '$rec/go-calls'
|
||||
exit 0
|
||||
});
|
||||
|
||||
open(my $fh, '>', "$root/build.sh") or die "write build.sh: $!";
|
||||
print {$fh} build_script();
|
||||
close($fh);
|
||||
|
||||
my $rc = system('/bin/bash', '-c',
|
||||
"cd '$work' && PATH=\"$bin:\$PATH\" SOURCE_DATE_EPOCH=1789413339 "
|
||||
. "bash '$root/build.sh' > '$root/build.log' 2>&1");
|
||||
|
||||
my $slurp = sub {
|
||||
my ($f) = @_;
|
||||
return () unless -f "$rec/$f";
|
||||
open(my $h, '<', "$rec/$f") or return ();
|
||||
my @l = <$h>; close($h); chomp @l; return @l;
|
||||
};
|
||||
open(my $lh, '<', "$root/build.log") or die "read build.log: $!";
|
||||
my $log = do { local $/; <$lh> };
|
||||
close($lh);
|
||||
return { rc => $rc, log => $log // '',
|
||||
curl => [ $slurp->('curl-urls') ],
|
||||
go_calls => [ $slurp->('go-calls') ],
|
||||
escapes => [ $slurp->('escapes') ] };
|
||||
}
|
||||
|
||||
# The architecture the toolchain must be built for: this machine's, in Go's spelling.
|
||||
my $host_deb = `dpkg --print-architecture 2>/dev/null` // '';
|
||||
chomp $host_deb;
|
||||
plan skip_all => 'dpkg is not available' unless $host_deb =~ /^[a-z0-9]+$/;
|
||||
my $host_go = $host_deb eq 'ppc64el' ? 'ppc64le' : $host_deb;
|
||||
|
||||
# ---- the cell that failed: a riscv64 chroot on this build host ----------------------------------
|
||||
my $r = run_build('riscv64');
|
||||
|
||||
my ($toolchain) = grep { m{/go[\d.]+\.linux-} } @{ $r->{curl} };
|
||||
ok(defined $toolchain, 'the build fetches a pinned Go toolchain')
|
||||
or diag("curl was asked for: @{ $r->{curl} }\n$r->{log}");
|
||||
|
||||
SKIP: {
|
||||
skip 'no toolchain download to inspect', 1 unless defined $toolchain;
|
||||
like($toolchain, qr/\.linux-\Q$host_go\E\.tar\.gz$/,
|
||||
"the toolchain is built for the build host ($host_go), so it runs natively not under qemu")
|
||||
or diag("fetched: $toolchain");
|
||||
}
|
||||
|
||||
# compiles_for($result, $goarch, $label): every `go build` the run reached was told to emit $goarch.
|
||||
# A run that reached NO `go build` fails here: an empty list would otherwise satisfy any claim.
|
||||
sub compiles_for {
|
||||
my ($res, $goarch, $label) = @_;
|
||||
my @builds = grep { /ARGV=.*\bbuild\b/ } @{ $res->{go_calls} };
|
||||
unless (@builds) {
|
||||
fail("$label -- the run never reached `go build`");
|
||||
diag("go was called: @{ $res->{go_calls} }\nrc=$res->{rc}\n$res->{log}");
|
||||
return;
|
||||
}
|
||||
is_deeply([ grep { !/\bGOARCH=\Q$goarch\E\b/ } @builds ], [], $label)
|
||||
or diag("go build calls:\n" . join("\n", @builds));
|
||||
}
|
||||
|
||||
compiles_for($r, 'riscv64',
|
||||
'every `go build` is told to emit riscv64, so the native toolchain cross-compiles');
|
||||
|
||||
is_deeply($r->{escapes}, [],
|
||||
'the build writes and deletes only inside its own build tree')
|
||||
or diag("escaped the build tree:\n" . join("\n", @{ $r->{escapes} }));
|
||||
|
||||
# ---- a chroot of the host's own architecture still builds natively ------------------------------
|
||||
my $n = run_build($host_deb);
|
||||
my ($native) = grep { m{/go[\d.]+\.linux-} } @{ $n->{curl} };
|
||||
like($native // '', qr/\.linux-\Q$host_go\E\.tar\.gz$/,
|
||||
'a native cell fetches the same toolchain');
|
||||
compiles_for($n, $host_go, 'a native cell compiles for its own architecture');
|
||||
|
||||
# ---- dpkg and Go spell the POWER architecture differently ----------------------------------------
|
||||
my $p = run_build('ppc64el');
|
||||
compiles_for($p, 'ppc64le', 'the dpkg name ppc64el reaches go as ppc64le');
|
||||
|
||||
done_testing;
|
||||
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/perl
|
||||
# Behaviour test for ipxe-xcat/verify-payload.pl, the check between a built ipxe-xcat package and the
|
||||
# release tree that the package must carry byte for byte.
|
||||
#
|
||||
# A manifest is written for a small fixture tree with the checker's own --generate mode. Each case
|
||||
# damages a copy of the tree in one way and runs the check as a subprocess: the check must fail and
|
||||
# name the path. A check that compared only names would pass every damage case below. The last block
|
||||
# holds the committed payload.sha256 and SHA256SUMS to the committed release archives.
|
||||
use strict;
|
||||
use warnings;
|
||||
use Test::More;
|
||||
use Digest::SHA ();
|
||||
use File::Basename qw(dirname);
|
||||
use File::Copy qw(copy);
|
||||
use File::Path qw(make_path);
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin qw($RealBin);
|
||||
use IPC::Open3 qw(open3);
|
||||
|
||||
my $pkg_dir = "$RealBin/../ipxe-xcat";
|
||||
my $checker = "$pkg_dir/verify-payload.pl";
|
||||
plan skip_all => 'ipxe-xcat/verify-payload.pl not found' unless -f $checker;
|
||||
|
||||
my $tmp = tempdir(CLEANUP => 1);
|
||||
|
||||
# Runs the checker; returns its exit code and its merged stdout and stderr.
|
||||
sub run_checker {
|
||||
my (@args) = @_;
|
||||
my $pid = open3(my $in, my $out, undef, $^X, $checker, @args);
|
||||
close($in);
|
||||
my $text = do { local $/; <$out> } // '';
|
||||
waitpid($pid, 0);
|
||||
return ($? >> 8, $text);
|
||||
}
|
||||
|
||||
sub write_file {
|
||||
my ($path, $content) = @_;
|
||||
make_path(dirname($path));
|
||||
open(my $fh, '>:raw', $path) or die "write $path: $!";
|
||||
print {$fh} $content;
|
||||
close($fh) or die "close $path: $!";
|
||||
}
|
||||
|
||||
sub make_tree {
|
||||
my ($root) = @_;
|
||||
write_file("$root/i386/undionly.kpxe", "undi\x00\x01");
|
||||
write_file("$root/x86_64-sb/snponly.efi", "MZ signed snponly");
|
||||
write_file("$root/x86_64-sb/shimx64.efi", "MZ signed shim");
|
||||
symlink('shimx64.efi', "$root/x86_64-sb/ipxe-shim.efi") or die "symlink: $!";
|
||||
symlink('x86_64-sb', "$root/sb") or die "symlink: $!";
|
||||
symlink('i386/undionly.kpxe', "$root/undionly.kpxe") or die "symlink: $!";
|
||||
}
|
||||
|
||||
sub copy_tree {
|
||||
my ($name) = @_;
|
||||
my $copy = "$tmp/$name";
|
||||
system('cp', '-a', "$tmp/pristine", $copy) == 0 or die "cp -a to $copy failed";
|
||||
return $copy;
|
||||
}
|
||||
|
||||
make_tree("$tmp/pristine");
|
||||
my ($code, $manifest_text) = run_checker('--generate', "$tmp/pristine");
|
||||
is($code, 0, '--generate succeeds on a tree of files, directories and symlinks');
|
||||
my $manifest = "$tmp/payload.sha256";
|
||||
write_file($manifest, $manifest_text);
|
||||
my @entries = grep { !/^#/ } split(/\n/, $manifest_text);
|
||||
is(scalar(@entries), 8, 'the manifest has one entry for each directory, file and symlink');
|
||||
like($manifest_text, qr/^link\tx86_64-sb\tsb$/m, 'a symlink is recorded with its target, not followed');
|
||||
|
||||
($code, my $output) = run_checker("$tmp/pristine", $manifest);
|
||||
is($code, 0, 'the unchanged tree passes') or diag($output);
|
||||
like($output, qr/payload matches .*: 8 entries/, 'the check reports the number of entries');
|
||||
($code, $output) = run_checker("$tmp/pristine/", $manifest);
|
||||
is($code, 0, 'a trailing slash on the tree path does not change the result') or diag($output);
|
||||
|
||||
my @damage = (
|
||||
['one changed byte',
|
||||
sub { write_file("$_[0]/i386/undionly.kpxe", "undi\x00\x02") },
|
||||
qr{^content changed: i386/undionly\.kpxe$}m],
|
||||
['a missing file',
|
||||
sub { unlink("$_[0]/x86_64-sb/snponly.efi") or die $! },
|
||||
qr{^missing: x86_64-sb/snponly\.efi$}m],
|
||||
['an extra file',
|
||||
sub { write_file("$_[0]/x86_64-sb/extra.efi", 'extra') },
|
||||
qr{^unexpected: x86_64-sb/extra\.efi$}m],
|
||||
['an extra directory',
|
||||
sub { make_path("$_[0]/arm64") },
|
||||
qr{^unexpected: arm64$}m],
|
||||
['a symlink with another target',
|
||||
sub { unlink("$_[0]/sb") or die $!; symlink('i386', "$_[0]/sb") or die $! },
|
||||
qr{^link target changed: sb \(expected x86_64-sb, found i386\)$}m],
|
||||
['a symlink replaced by a copy of its target',
|
||||
sub { unlink("$_[0]/x86_64-sb/ipxe-shim.efi") or die $!;
|
||||
write_file("$_[0]/x86_64-sb/ipxe-shim.efi", "MZ signed shim") },
|
||||
qr{^type changed: x86_64-sb/ipxe-shim\.efi \(expected link, found file\)$}m],
|
||||
);
|
||||
my $n = 0;
|
||||
for my $case (@damage) {
|
||||
my ($name, $apply, $message) = @{$case};
|
||||
my $copy = copy_tree('damaged-' . ++$n);
|
||||
$apply->($copy);
|
||||
($code, $output) = run_checker($copy, $manifest);
|
||||
is($code, 1, "$name fails the check");
|
||||
like($output, $message, "$name is reported by path") or diag($output);
|
||||
}
|
||||
|
||||
write_file("$tmp/malformed.sha256", "file\tnot-a-digest\ti386/undionly.kpxe\n");
|
||||
($code) = run_checker("$tmp/pristine", "$tmp/malformed.sha256");
|
||||
is($code, 2, 'a malformed manifest line is an error, not a mismatch');
|
||||
write_file("$tmp/duplicate.sha256", "dir\t-\ti386\ndir\t-\ti386\n");
|
||||
($code) = run_checker("$tmp/pristine", "$tmp/duplicate.sha256");
|
||||
is($code, 2, 'a duplicate manifest path is an error');
|
||||
($code) = run_checker("$tmp/pristine/sb", $manifest);
|
||||
is($code, 2, 'a tree path that is a symlink is refused');
|
||||
|
||||
# The committed manifest and checksums must describe the committed archives.
|
||||
my @releases = glob("$pkg_dir/ipxeboot-*.tar.gz");
|
||||
is(scalar(@releases), 1, 'the package directory holds one release archive');
|
||||
SKIP: {
|
||||
skip 'no release archive', 4 if @releases != 1;
|
||||
my $release = "$tmp/release";
|
||||
make_path($release);
|
||||
is(system('tar', '-xzf', $releases[0], '--strip-components=1', '-C', $release), 0,
|
||||
'the release archive unpacks');
|
||||
|
||||
# The builders install the ipxe/shim shims, signed by both UEFI CAs, over those of the release.
|
||||
my %shims = ('ipxe-shimx64.efi' => 'x86_64-sb/shimx64.efi', 'ipxe-shimaa64.efi' => 'arm64-sb/shimaa64.efi');
|
||||
($code, $output) = run_checker($release, "$pkg_dir/payload.sha256");
|
||||
is($code, 1, 'payload.sha256 does not describe the shims of the bare release tree');
|
||||
copy("$pkg_dir/$_", "$release/$shims{$_}") or die "copy $_: $!" for sort keys %shims;
|
||||
($code, $output) = run_checker($release, "$pkg_dir/payload.sha256");
|
||||
is($code, 0, 'payload.sha256 matches the release tree with the committed shims') or diag($output);
|
||||
|
||||
open(my $fh, '<', "$pkg_dir/SHA256SUMS") or die "read SHA256SUMS: $!";
|
||||
my %sums = map { /^([0-9a-f]{64}) (\S+)$/ ? ($2, $1) : () } <$fh>;
|
||||
close($fh);
|
||||
my %actual = map { ($_, Digest::SHA->new(256)->addfile("$pkg_dir/$_", 'b')->hexdigest) }
|
||||
grep { -f "$pkg_dir/$_" } keys %sums;
|
||||
is_deeply(\%actual, \%sums, 'SHA256SUMS matches the committed archives and shims');
|
||||
}
|
||||
|
||||
done_testing();
|
||||
+46
-2
@@ -10,9 +10,10 @@ use lib "$RealBin/..";
|
||||
use File::Temp qw(tempdir);
|
||||
use File::Path qw(make_path);
|
||||
use File::Basename qw(basename);
|
||||
use File::Slurper qw(write_text);
|
||||
use MockBuildUtils qw(install_deps_packages install_deps_command missing_perl_modules
|
||||
required_pkgs version_matches rpm_sigmd5 rpm_version rpm_release rpm_is_signed
|
||||
rpm_arch rpm_in_cell
|
||||
rpm_arch rpm_in_cell resolve_mock_cfg
|
||||
skipped_builder carry_over_rpms source_package
|
||||
restamp_release_line cross_copy_genesis finalize_xcat_dep read_manifest
|
||||
verify_repo_packages verify_repo_signature verify_rpm_signatures
|
||||
@@ -31,6 +32,24 @@ sub quiet(&) {
|
||||
return wantarray ? @r : $r[0];
|
||||
}
|
||||
|
||||
# ---- resolve_mock_cfg: /etc/os-release says almalinux, mock-core-configs names the file alma -----
|
||||
{
|
||||
my $dir = tempdir(CLEANUP => 1);
|
||||
my $touch = sub { open(my $fh, '>', "$dir/$_[0].cfg") or die "$_[0]: $!"; close($fh); };
|
||||
$touch->('alma+epel-10-x86_64');
|
||||
$touch->('rocky+epel-9-x86_64');
|
||||
is(eval { resolve_mock_cfg('almalinux', 10, 'x86_64', $dir) }, 'alma+epel-10-x86_64',
|
||||
'an AlmaLinux host resolves to the alma config file');
|
||||
is(eval { resolve_mock_cfg('rocky', 9, 'x86_64', $dir) }, 'rocky+epel-9-x86_64',
|
||||
'an id that names its config file resolves to it');
|
||||
$touch->('almalinux+epel-10-x86_64');
|
||||
is(eval { resolve_mock_cfg('almalinux', 10, 'x86_64', $dir) }, 'almalinux+epel-10-x86_64',
|
||||
'a config file named after the os-release id is preferred');
|
||||
ok(!eval { resolve_mock_cfg('almalinux', 8, 'x86_64', $dir); 1 },
|
||||
'a release with no config file is an error');
|
||||
like($@, qr{\Q$dir/alma+epel-8-x86_64.cfg\E}, 'the error names the config files it tried');
|
||||
}
|
||||
|
||||
# ---- required_pkgs: a skipped builder's packages are not required (clean --skip-* runs) -------
|
||||
my @all = qw(elilo-xcat ipmitool-xcat perl-IO-Stty perl-Sys-Virt xCAT-genesis-base);
|
||||
is_deeply([required_pkgs(\@all, 0, 0, 0)], \@all,
|
||||
@@ -363,6 +382,27 @@ SPEC
|
||||
is($tarch{ppc64le}, 'ppc64', 'GENESIS_ARCHES: ppc64le maps to xCAT tarch ppc64');
|
||||
}
|
||||
|
||||
# ---- finalize_xcat_dep only => [...]: write only the cells of the named arches -----------------
|
||||
# Each host finalizes the cells it deploys, so a cell lock is never held from another host.
|
||||
{
|
||||
my $tmp = tempdir(CLEANUP => 1);
|
||||
my ($x, $p) = ("$tmp/r/rh9/x86_64", "$tmp/r/rh9/ppc64le");
|
||||
make_path($x, $p);
|
||||
write_text("$x/xCAT-genesis-base-x86_64-2.19.1-1.noarch.rpm", "x86 genesis\n");
|
||||
write_text("$p/xCAT-genesis-base-ppc64-2.19.1-1.noarch.rpm", "ppc genesis\n");
|
||||
my @reindexed;
|
||||
quiet { finalize_xcat_dep("$tmp/r", "$tmp/r", only => ['ppc64le'],
|
||||
reindex => sub { push @reindexed, $_[0] }) };
|
||||
ok(-f "$p/xCAT-genesis-base-x86_64-2.19.1-1.noarch.rpm", 'the ppc64le cell gets the x86_64 genesis');
|
||||
ok(!-e "$x/xCAT-genesis-base-ppc64-2.19.1-1.noarch.rpm", 'the x86_64 cell is not written');
|
||||
is_deeply(\@reindexed, [$p], 'only the ppc64le cell is re-indexed');
|
||||
|
||||
my $bad = eval { quiet { finalize_xcat_dep("$tmp/r", "$tmp/r", only => ['riscv64']) }; 1 };
|
||||
ok(!$bad, 'an arch outside the cross-arch matrix is refused');
|
||||
like($@, qr/\AFATAL: \[finalize\] no cross-arch genesis for arch 'riscv64'/,
|
||||
'the refusal names the arch');
|
||||
}
|
||||
|
||||
# ---- restamp_release_line: CD --build-number Release stamping (PR #62 review point 1) ----------
|
||||
# A fresh stamp is appended after the Release token, preserving any %{?dist} macro.
|
||||
{
|
||||
@@ -415,6 +455,10 @@ is(rpm_release(tempdir(CLEANUP => 1), 'nonexistent-pkg'), undef, 'rpm_release is
|
||||
cmp_ok(scalar(@targets), '>=', 1, 'packages-manifest.conf has at least one target section');
|
||||
ok(!grep({ $_ eq 'common' } @targets), 'the shared-repo section is not treated as a build target');
|
||||
my @missing = grep { !exists $m{$_}{'conserver-xcat'} } @targets;
|
||||
# The upstream iPXE loaders ship beside xnba-undi, so a target that publishes one publishes both.
|
||||
my @no_ipxe_xcat = grep { exists $m{$_}{'xnba-undi'} && !exists $m{$_}{'ipxe-xcat'} } @targets;
|
||||
is_deeply(\@no_ipxe_xcat, [], 'every target that lists xnba-undi also lists ipxe-xcat')
|
||||
or diag("missing ipxe-xcat in: @no_ipxe_xcat");
|
||||
is_deeply(\@missing, [], 'conserver-xcat is present in every manifest target section')
|
||||
or diag("missing conserver-xcat in: @missing");
|
||||
|
||||
@@ -422,7 +466,7 @@ is(rpm_release(tempdir(CLEANUP => 1), 'nonexistent-pkg'), undef, 'rpm_release is
|
||||
# carries, at the same pins: a riscv64 MN serves the x86 nodes of a mixed cluster too.
|
||||
my ($ppc) = grep { /^[a-z+]+-10-ppc64le$/ } @targets;
|
||||
ok(defined $ppc, 'an EL10 ppc64le target section exists to compare against') or $ppc = '';
|
||||
for my $boot (qw(elilo-xcat grub2-xcat syslinux-xcat xnba-undi)) {
|
||||
for my $boot (qw(elilo-xcat grub2-xcat ipxe-xcat syslinux-xcat xnba-undi)) {
|
||||
is($m{'rocky-10-riscv64-xcat'}{$boot}, $m{$ppc}{$boot},
|
||||
"$boot pinned in the riscv64 target as in the EL10 ppc64le target");
|
||||
}
|
||||
|
||||
+310
@@ -0,0 +1,310 @@
|
||||
#!/usr/bin/perl
|
||||
# XCAT::NFSLock: the NFS lock protocol at the top of lib/XCAT/NFSLock.pm.
|
||||
use strict;
|
||||
use warnings;
|
||||
use Test::More;
|
||||
use FindBin qw($RealBin);
|
||||
use lib "$RealBin/../lib";
|
||||
use File::Temp qw(tempdir);
|
||||
use File::Slurper qw(read_text write_text);
|
||||
use POSIX ();
|
||||
use Time::HiRes ();
|
||||
|
||||
my $renames = 0;
|
||||
my @mkdirs;
|
||||
|
||||
BEGIN {
|
||||
no warnings 'once';
|
||||
*CORE::GLOBAL::rename = sub { $renames++; return CORE::rename($_[0], $_[1]) };
|
||||
*CORE::GLOBAL::mkdir = sub { push(@mkdirs, $_[0]); return @_ > 1 ? CORE::mkdir($_[0], $_[1]) : CORE::mkdir($_[0]) };
|
||||
}
|
||||
|
||||
use XCAT::NFSLock qw(this_process format_metadata parse_metadata owner_is_dead process_start);
|
||||
|
||||
# The lock logs to the selected handle. Keep the log out of the TAP stream, and read it back.
|
||||
open(my $log_fh, '>', \my $logged) or die "Cannot capture the log: $!";
|
||||
select($log_fh);
|
||||
|
||||
sub clear_log { $logged = ''; seek($log_fh, 0, 0) }
|
||||
|
||||
my $dir = tempdir(CLEANUP => 1);
|
||||
my $me = this_process();
|
||||
is($me->{pid}, $$, 'the identity names this process');
|
||||
is($me->{pstart}, process_start($$), 'the identity carries the start time of this process');
|
||||
isnt(this_process()->{token}, $me->{token}, 'each identity has a fresh token');
|
||||
|
||||
# No process can have a pid above the kernel's pid_max (2**22 at most).
|
||||
my $gone = 2**22 + 7;
|
||||
is(process_start($gone), undef, 'a pid that names no process has no start time');
|
||||
|
||||
my $parent = getppid();
|
||||
my $parent_start = process_start($parent);
|
||||
|
||||
sub metadata {
|
||||
my (%f) = @_;
|
||||
return format_metadata({ %$me, token => 'ab' x 16, %f });
|
||||
}
|
||||
|
||||
sub stage {
|
||||
my ($name, $text) = @_;
|
||||
my $path = "$dir/$name";
|
||||
mkdir($path) or die "Cannot stage $path: $!";
|
||||
write_text("$path/metadata", $text) if defined($text);
|
||||
return $path;
|
||||
}
|
||||
|
||||
# Record the waits instead of sleeping. $on_sleep runs at each wait.
|
||||
my @slept;
|
||||
our $on_sleep;
|
||||
{
|
||||
no warnings 'redefine';
|
||||
*XCAT::NFSLock::_sleep = sub { push(@slept, $_[0]); $on_sleep->() if $on_sleep };
|
||||
}
|
||||
|
||||
# Metadata: fields, hash, validation.
|
||||
{
|
||||
my $text = metadata();
|
||||
is_deeply(parse_metadata($text), { %$me, token => 'ab' x 16 }, 'valid metadata parses to its identity');
|
||||
like($text, qr/\Aboot-id=.*\nmachine-id=.*\npid=.*\npstart=.*\ntoken=.*\nhash=[0-9a-f]{64}\n\z/,
|
||||
'the fields are sorted and the hash comes last');
|
||||
is(parse_metadata(substr($text, 0, length($text) - 10)), undef, 'a partial read is invalid');
|
||||
is(parse_metadata($text =~ s/pid=\d+/pid=1/r), undef, 'a changed field no longer matches the hash');
|
||||
is(parse_metadata($text =~ s/^token=.*\n//mr), undef, 'a missing field is invalid');
|
||||
is(parse_metadata("extra=1\n$text"), undef, 'an unknown field is invalid');
|
||||
is(parse_metadata(undef), undef, 'missing metadata is invalid');
|
||||
}
|
||||
|
||||
# A free lock is taken and released without leftovers.
|
||||
{
|
||||
my $path = "$dir/free.lock";
|
||||
my $lock = XCAT::NFSLock->acquire($path);
|
||||
my $meta = parse_metadata(read_text("$path/metadata"));
|
||||
is($meta && $meta->{pid}, $$, 'a free lock is taken with the identity of this process');
|
||||
is($lock->release, 1, 'the owner releases its lock');
|
||||
ok(!-e $path, 'the released lock.d is gone');
|
||||
ok(!-e "$path.borrow", 'release removes lock.borrow');
|
||||
is($lock->release, 0, 'a second release does nothing');
|
||||
}
|
||||
|
||||
for my $bad ('', "$dir/.", "$dir/..", "$dir/") {
|
||||
eval { XCAT::NFSLock->acquire($bad); 1 };
|
||||
like($@, qr/\AInvalid lock path/, "a lock path that names no entry is refused: '$bad'");
|
||||
}
|
||||
|
||||
for my $case (
|
||||
[ { retries => 0 }, qr/\AInvalid retries 0 for lock/ ],
|
||||
[ { retries => 1.5 }, qr/\AInvalid retries 1\.5 for lock/ ],
|
||||
[ { delay => 2.9 }, qr/\AInvalid delay 2\.9 for lock: must be 3s or more/ ],
|
||||
[ { jitter => -1 }, qr/\AInvalid jitter -1 for lock: must be 0 or more/ ],
|
||||
[ { delay => 3, jitter => 1.5 }, qr/\AInvalid jitter 1\.5 for lock: must be less than half the delay/ ],
|
||||
)
|
||||
{
|
||||
my ($opt, $error) = @$case;
|
||||
eval { XCAT::NFSLock->acquire("$dir/bad-option.lock", %$opt); 1 };
|
||||
like($@, $error, 'an invalid retry option is refused: ' . join(',', %$opt));
|
||||
ok(!-e "$dir/bad-option.lock", 'an invalid option creates no lock');
|
||||
}
|
||||
|
||||
# Retry: R waits of T ± δ, then an error that names the lock.
|
||||
{
|
||||
my $path = stage('retried.lock', metadata(pid => $parent, pstart => $parent_start));
|
||||
@slept = ();
|
||||
eval { XCAT::NFSLock->acquire($path, retries => 4, delay => 5, jitter => 2, label => 'cell lock'); 1 };
|
||||
like($@, qr/\ATrying to unlock \Q$path\E failed after 4 retries; cell lock owned by pid $parent on machine /,
|
||||
'the error names the lock, the retries and the owner');
|
||||
is(scalar(@slept), 4, 'acquire waits R times');
|
||||
is(scalar(grep { $_ >= 3 && $_ <= 7 } @slept), 4, 'each wait is within T ± δ');
|
||||
|
||||
@slept = ();
|
||||
eval { XCAT::NFSLock->acquire($path, timeout => 10); 1 };
|
||||
like($@, qr/failed after 4 retries;/, 'a timeout of 10s with the default delay of 3s is 4 retries');
|
||||
@slept = ();
|
||||
eval { XCAT::NFSLock->acquire($path); 1 };
|
||||
like($@, qr/failed after 1 retry;/, 'a lock with no timeout still retries once');
|
||||
}
|
||||
|
||||
# The lock stays with an owner that is not proven dead.
|
||||
for my $case (
|
||||
[ 'live owner on this machine', metadata(pid => $parent, pstart => $parent_start) ],
|
||||
[ 'owner on another machine', metadata('machine-id' => 'elsewhere', pid => $gone) ],
|
||||
[ 'partial metadata', substr(metadata(pid => $gone), 0, 40) ],
|
||||
[ 'metadata with a bad hash', metadata(pid => $gone) =~ s/hash=(.)/'hash=' . ($1 eq '0' ? '1' : '0')/er ],
|
||||
[ 'no metadata', undef ],
|
||||
)
|
||||
{
|
||||
my ($name, $text) = @$case;
|
||||
(my $file = "$name.lock") =~ s/\s+/-/g;
|
||||
my $path = stage($file, $text);
|
||||
@mkdirs = ();
|
||||
eval { XCAT::NFSLock->acquire($path, retries => 2); 1 };
|
||||
like($@, qr/\ATrying to unlock \Q$path\E failed after 2 retries;/, "$name: the lock is not taken");
|
||||
is(scalar(grep { $_ eq "$path.borrow" } @mkdirs), $name =~ /live owner/ ? 3 : 0,
|
||||
"$name: lock.borrow is tried only for an owner on this machine with valid metadata");
|
||||
is(-e "$path/metadata" ? read_text("$path/metadata") : undef, $text, "$name: the metadata is unchanged");
|
||||
ok(!-e "$path.borrow", "$name: lock.borrow is not left behind");
|
||||
}
|
||||
|
||||
# A dead owner on this machine loses the lock.
|
||||
for my $case (
|
||||
[ 'process gone', metadata(pid => $gone) ],
|
||||
[ 'pid reused', metadata(pid => $parent, pstart => $parent_start + 1) ],
|
||||
[ 'machine rebooted', metadata('boot-id' => 'an-earlier-boot', pid => $parent, pstart => $parent_start) ],
|
||||
)
|
||||
{
|
||||
my ($name, $text) = @$case;
|
||||
(my $file = "$name.lock") =~ s/\s+/-/g;
|
||||
my $path = stage($file, $text);
|
||||
@slept = ();
|
||||
my $lock = eval { XCAT::NFSLock->acquire($path) };
|
||||
ok($lock, "$name: the lock of a dead owner is taken") or diag($@);
|
||||
is(scalar(@slept), 0, "$name: the lock is taken without a wait");
|
||||
my $meta = parse_metadata(read_text("$path/metadata"));
|
||||
is($meta && $meta->{pid}, $$, "$name: the metadata now names this process");
|
||||
isnt($meta && $meta->{token}, 'ab' x 16, "$name: the new metadata has a fresh token");
|
||||
ok(!-e "$path.borrow", "$name: lock.borrow is removed");
|
||||
is($lock->release, 1, "$name: the new owner releases the lock") if $lock;
|
||||
}
|
||||
|
||||
# Another process holds lock.borrow: this one does not take the lock.
|
||||
{
|
||||
my $dead = metadata(pid => $gone);
|
||||
my $path = stage('borrowed.lock', $dead);
|
||||
mkdir("$path.borrow") or die "Cannot stage $path.borrow: $!";
|
||||
eval { XCAT::NFSLock->acquire($path, retries => 2); 1 };
|
||||
like($@, qr/\ATrying to unlock /, 'a lock under another borrower is not taken');
|
||||
is(read_text("$path/metadata"), $dead, 'the metadata under another borrower is unchanged');
|
||||
ok(-d "$path.borrow", 'the other borrower keeps lock.borrow');
|
||||
}
|
||||
|
||||
# The owner changed between step 2 and step 4: this attempt does not take the lock,
|
||||
# even when the new owner is dead too.
|
||||
{
|
||||
my $path = stage('changed.lock', metadata(pid => $gone));
|
||||
my $other = metadata(pid => $gone, token => 'cd' x 16);
|
||||
my $read = \&XCAT::NFSLock::_read_metadata;
|
||||
my $reads = 0;
|
||||
no warnings 'redefine';
|
||||
local *XCAT::NFSLock::_read_metadata = sub {
|
||||
write_text("$path/metadata", $other) if ++$reads == 2;
|
||||
return $read->(@_);
|
||||
};
|
||||
@slept = ();
|
||||
my $lock = eval { XCAT::NFSLock->acquire($path, retries => 1) };
|
||||
ok($lock, 'the lock is taken on the next attempt') or diag($@);
|
||||
is(scalar(@slept), 1, 'a changed owner costs one retry');
|
||||
ok(!-e "$path.borrow", 'lock.borrow is removed');
|
||||
$lock->release if $lock;
|
||||
}
|
||||
|
||||
# release waits for a process that holds lock.borrow.
|
||||
{
|
||||
my $path = "$dir/release-borrowed.lock";
|
||||
my $lock = XCAT::NFSLock->acquire($path);
|
||||
mkdir("$path.borrow") or die "Cannot stage $path.borrow: $!";
|
||||
@slept = ();
|
||||
local $on_sleep = sub { rmdir("$path.borrow") };
|
||||
is($lock->release, 1, 'release removes the lock once lock.borrow is free');
|
||||
is(scalar(@slept), 1, 'release waits while lock.borrow is held');
|
||||
ok(!-e $path && !-e "$path.borrow", 'lock.d and lock.borrow are gone');
|
||||
}
|
||||
|
||||
# A forked child of the owner does not release the lock.
|
||||
{
|
||||
my $path = "$dir/forked.lock";
|
||||
my $lock = XCAT::NFSLock->acquire($path);
|
||||
my $child = fork() // die "Cannot fork: $!";
|
||||
POSIX::_exit($lock->release ? 1 : 0) if $child == 0;
|
||||
waitpid($child, 0);
|
||||
is($? >> 8, 0, 'the child reports that it released nothing');
|
||||
ok(-d $path, 'the lock survives the child');
|
||||
is($lock->release, 1, 'the owner still releases it');
|
||||
}
|
||||
|
||||
# The metadata names another acquisition: release leaves the lock alone.
|
||||
{
|
||||
my $path = "$dir/replaced.lock";
|
||||
my $lock = XCAT::NFSLock->acquire($path);
|
||||
my $other = metadata(pid => $parent, pstart => $parent_start);
|
||||
write_text("$path/metadata", $other);
|
||||
is($lock->release, 0, 'release does not remove a lock of another acquisition');
|
||||
is(read_text("$path/metadata"), $other, 'the other owner keeps its lock');
|
||||
ok(!-e "$path.borrow", 'release removes lock.borrow');
|
||||
}
|
||||
|
||||
# One process takes the lock once: a second acquire is another owner.
|
||||
{
|
||||
my $path = "$dir/twice.lock";
|
||||
my $first = XCAT::NFSLock->acquire($path);
|
||||
my $second = eval { XCAT::NFSLock->acquire($path) };
|
||||
ok(!$second, 'a second acquire in the same process does not take a held lock');
|
||||
is($first->release, 1, 'the first owner still holds and releases it');
|
||||
}
|
||||
|
||||
# A waiter takes the lock once its live owner releases it.
|
||||
{
|
||||
my $path = "$dir/handover.lock";
|
||||
pipe(my $ready_r, my $ready_w) or die "Cannot pipe: $!";
|
||||
my $child = fork() // die "Cannot fork: $!";
|
||||
if ($child == 0) {
|
||||
close($ready_r);
|
||||
my $held = XCAT::NFSLock->acquire($path);
|
||||
syswrite($ready_w, "x");
|
||||
Time::HiRes::sleep(0.5);
|
||||
$held->release;
|
||||
POSIX::_exit(0);
|
||||
}
|
||||
close($ready_w);
|
||||
sysread($ready_r, my $byte, 1);
|
||||
local $on_sleep = sub { waitpid($child, 0) };
|
||||
my $lock = eval { XCAT::NFSLock->acquire($path, retries => 1) };
|
||||
ok($lock, 'a waiter takes the lock after its owner releases it') or diag($@);
|
||||
$lock->release if $lock;
|
||||
}
|
||||
|
||||
# owner_is_dead decides from facts only.
|
||||
{
|
||||
my %here = ('machine-id' => 'm1', 'boot-id' => 'b1', start_of => sub { $_[0] == 10 ? 100 : undef });
|
||||
my %rec = ('machine-id' => 'm1', 'boot-id' => 'b1', pid => 10, pstart => 100);
|
||||
is(owner_is_dead(undef, \%here), 0, 'invalid metadata is not proven dead');
|
||||
is(owner_is_dead({%rec}, \%here), 0, 'a live owner is not dead');
|
||||
is(owner_is_dead({ %rec, 'machine-id' => 'm2', pid => 11 }, \%here), 0,
|
||||
'another machine proves nothing, even for a pid that is free here');
|
||||
is(owner_is_dead({ %rec, 'boot-id' => 'b0' }, \%here), 1, 'an owner from an earlier boot is dead');
|
||||
is(owner_is_dead({ %rec, pid => 11 }, \%here), 1, 'an owner whose pid is gone is dead');
|
||||
is(owner_is_dead({ %rec, pstart => 99 }, \%here), 1, 'an owner whose pid was reused is dead');
|
||||
}
|
||||
|
||||
# The log names each event, in the order it happened.
|
||||
{
|
||||
my $stamp = qr/\[nfslock\] \d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z \S+ pid=$$/;
|
||||
my $path = "$dir/logged.lock";
|
||||
clear_log();
|
||||
my $lock = XCAT::NFSLock->acquire($path, label => 'cell lock');
|
||||
$lock->release;
|
||||
my @lines = split(/\n/, $logged);
|
||||
is(scalar(@lines), 2, 'a lock taken and released logs two lines');
|
||||
like($lines[0], qr/\A$stamp acquired cell lock \Q$path\E\z/, 'the first line is the acquisition');
|
||||
like($lines[1], qr/\A$stamp released cell lock \Q$path\E\z/, 'the second line is the release');
|
||||
|
||||
my $held = stage('logged-wait.lock', metadata(pid => $parent, pstart => $parent_start));
|
||||
clear_log();
|
||||
eval { XCAT::NFSLock->acquire($held, retries => 2); 1 };
|
||||
my @waits = $logged =~ /^$stamp wait lock \Q$held\E (retry \d\/\d), owner pid $parent on machine /mg;
|
||||
is_deeply(\@waits, ['retry 1/2', 'retry 2/2'], 'each retry logs a wait that names the owner');
|
||||
|
||||
my $dead = stage('logged-dead.lock', metadata(pid => $gone));
|
||||
clear_log();
|
||||
my $taken = XCAT::NFSLock->acquire($dead);
|
||||
like($logged, qr/^$stamp took-over lock \Q$dead\E from dead pid $gone$/m, 'a takeover names the dead owner');
|
||||
$taken->release;
|
||||
|
||||
clear_log();
|
||||
my $quiet = XCAT::NFSLock->acquire("$dir/quiet.lock", quiet => 1);
|
||||
$quiet->release;
|
||||
eval { XCAT::NFSLock->acquire($held, retries => 1, quiet => 1); 1 };
|
||||
is($logged, '', 'quiet => 1 logs nothing');
|
||||
}
|
||||
|
||||
is($renames, 0, 'the lock never renames');
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/perl
|
||||
# Every architecture run of a dep build can publish the shared common/ tree. Recovery of an
|
||||
# interrupted publication removes staging trees, so it runs only under the common lock, and never
|
||||
# while another run holds that lock.
|
||||
use strict;
|
||||
use warnings;
|
||||
use Test::More;
|
||||
use FindBin qw($RealBin);
|
||||
use lib "$RealBin/..", "$RealBin/../lib";
|
||||
use File::Temp qw(tempdir);
|
||||
use File::Path qw(make_path);
|
||||
use File::Slurper qw(read_text write_text);
|
||||
use MockBuildUtils qw(recover_common_repository);
|
||||
use XCAT::NFSLock qw(this_process format_metadata process_start);
|
||||
|
||||
my $me = this_process();
|
||||
|
||||
# No process can have a pid above the kernel's pid_max (2**22 at most).
|
||||
my $gone = 2**22 + 7;
|
||||
my $parent = getppid();
|
||||
|
||||
sub record {
|
||||
my (%f) = @_;
|
||||
return format_metadata({ %$me, %f });
|
||||
}
|
||||
|
||||
# A repository left by an interrupted publication: common/ moved aside, a staging tree beside it.
|
||||
sub interrupted {
|
||||
my ($holder) = @_;
|
||||
my $base = tempdir(CLEANUP => 1);
|
||||
make_path("$base/.common.previous.999", "$base/.common.staging");
|
||||
write_text("$base/.common.previous.999/marker", "previous repository\n");
|
||||
if (defined($holder)) {
|
||||
make_path("$base/.common-publish.lock");
|
||||
write_text("$base/.common-publish.lock/metadata", $holder);
|
||||
}
|
||||
return $base;
|
||||
}
|
||||
|
||||
{
|
||||
my $base = interrupted(undef);
|
||||
is(recover_common_repository($base), 1, 'recovery runs when nobody holds the common lock');
|
||||
is(read_text("$base/common/marker"), "previous repository\n", 'the interrupted common tree is restored');
|
||||
ok(!-e "$base/.common.staging", 'the abandoned staging tree is removed');
|
||||
ok(!-e "$base/.common-publish.lock", 'recovery releases the common lock');
|
||||
}
|
||||
|
||||
for my $case (
|
||||
[ 'a live run on this host', record(pid => $parent, pstart => process_start($parent)) ],
|
||||
[ 'a run on another host', record('machine-id' => 'elsewhere', pid => $gone) ],
|
||||
)
|
||||
{
|
||||
my ($name, $holder) = @$case;
|
||||
my $base = interrupted($holder);
|
||||
open(my $capture, '>', \my $printed) or die "Cannot capture output: $!";
|
||||
my $previous = select($capture);
|
||||
my $ran = recover_common_repository($base);
|
||||
select($previous);
|
||||
is($ran, 0, "recovery does not run while $name holds the common lock");
|
||||
like($printed, qr/^common recovery skipped: another run holds \Q$base\E\/\.common-publish\.lock$/m,
|
||||
"the skip names the lock $name holds");
|
||||
ok(-d "$base/.common.staging", "the staging tree of $name is left in place");
|
||||
ok(!-e "$base/common", "the common tree stays where $name put it");
|
||||
is(read_text("$base/.common-publish.lock/metadata"), $holder, "$name keeps the common lock");
|
||||
}
|
||||
|
||||
{
|
||||
my $base = interrupted(record(pid => $gone));
|
||||
is(recover_common_repository($base), 1, 'recovery takes the common lock of a run that died on this host');
|
||||
ok(!-e "$base/.common.staging", 'the staging tree of the dead run is removed');
|
||||
ok(-d "$base/common", 'the common tree of the dead run is restored');
|
||||
}
|
||||
|
||||
done_testing();
|
||||
+21
-43
@@ -20,7 +20,7 @@ use BuildUtils qw(install_deps_packages install_deps_command missing_perl_module
|
||||
supported_arches is_supported_arch
|
||||
codename_to_version version_to_codename known_codenames
|
||||
chroot_name chroot_sources_list chroot_is_disposable chroot_build_script
|
||||
control_field genesis_deb_control
|
||||
control_field
|
||||
deb_field deb_version deb_hash cross_copy_genesis_deb
|
||||
build_deb_in_chroot);
|
||||
|
||||
@@ -88,41 +88,6 @@ is(chroot_name('noble', 'amd64'), 'noble-amd64-sbuild', 'chroot_name shape');
|
||||
is(control_field($ctrl, 'Replaces'), undef, 'absent field -> undef');
|
||||
}
|
||||
|
||||
# ---- genesis_deb_control: PRESERVE the maintained packaging semantics (concern #2) --------------
|
||||
{
|
||||
# The real xCAT-genesis-builder/debian/control fields that the bare 5-field shim used to drop.
|
||||
my $maintained = <<'CTRL';
|
||||
Source: xcat-genesis-base-amd64
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Maintainer: xCAT <xcat-user@lists.sourceforge.net>
|
||||
|
||||
Package: xcat-genesis-base-amd64
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}
|
||||
Replaces: xcat-genesis-amd64
|
||||
Breaks: xcat-genesis-amd64, xcat-genesis-scripts-amd64 (<< 2.13.10)
|
||||
Description: xCAT Genesis netboot image
|
||||
base platform.
|
||||
CTRL
|
||||
my $c = genesis_deb_control($maintained, 'xcat-genesis-base-amd64', '2.18.0-snap1', 'all');
|
||||
like($c, qr/^Package: xcat-genesis-base-amd64$/m, 'Package set');
|
||||
like($c, qr/^Version: 2\.18\.0-snap1$/m, 'Version set');
|
||||
like($c, qr/^Architecture: all$/m, 'Architecture set');
|
||||
like($c, qr/^Replaces: xcat-genesis-amd64$/m, 'Replaces PRESERVED (was dropped by the shim)');
|
||||
like($c, qr/^Breaks: xcat-genesis-amd64, xcat-genesis-scripts-amd64 \(<< 2\.13\.10\)$/m,
|
||||
'Breaks PRESERVED with its version constraint');
|
||||
unlike($c, qr/\$\{misc:Depends\}/, 'unresolved ${misc:Depends} substvar dropped (would ship literal)');
|
||||
like($c, qr/^Maintainer: xCAT /m, 'Maintainer preserved');
|
||||
}
|
||||
# With no maintained control available, an honest minimal control is still produced.
|
||||
{
|
||||
my $c = genesis_deb_control(undef, 'xcat-genesis-base-ppc64el', '2.18.0-snap1', 'all');
|
||||
like($c, qr/^Package: xcat-genesis-base-ppc64el$/m, 'minimal control still names the package');
|
||||
like($c, qr/^Architecture: all$/m, 'minimal control still arch:all');
|
||||
unlike($c, qr/^Replaces:/m, 'no Replaces invented when the maintained control is absent');
|
||||
}
|
||||
|
||||
# ---- verify_repo_packages: PURE completeness decision (no I/O; manifest = source of truth) -------
|
||||
{
|
||||
my %req = ('ipmitool-xcat' => '1.8.18', 'goconserver' => '0.3.3', 'xcat-genesis-base' => '*');
|
||||
@@ -337,14 +302,15 @@ SKIP: {
|
||||
is_deeply(\@miss_go, [], 'goconserver present in every manifest target')
|
||||
or diag("missing goconserver in: @miss_go");
|
||||
|
||||
# The noarch boot components (syslinux-xcat, grub2-xcat, elilo-xcat, xnba-undi) are Architecture:all
|
||||
# single-producer (built ONCE on amd64) but REQUIRED-PRESENT on EVERY target incl. ppc64el and
|
||||
# riscv64, so the gate verifies those repos actually carry them (matches the EL manifest + the 2.16
|
||||
# ppc dep repo; a ppc or riscv64 MN serves the x86 nodes of a mixed cluster). It is the BUILD PHASE
|
||||
# -- not the manifest -- that avoids rebuilding them off amd64 (build_one_codename skips an
|
||||
# Architecture:all package on non-amd64; see the control_binary_arch test below).
|
||||
# The noarch boot components (syslinux-xcat, grub2-xcat, elilo-xcat, xnba-undi, ipxe-xcat) are
|
||||
# Architecture:all single-producer (built ONCE on amd64) but REQUIRED-PRESENT on EVERY target
|
||||
# incl. ppc64el and riscv64, so the gate verifies those repos actually carry them (matches the
|
||||
# EL manifest + the 2.16 ppc dep repo; a ppc or riscv64 MN serves the x86 nodes of a mixed
|
||||
# cluster). It is the BUILD PHASE -- not the manifest -- that avoids rebuilding them off amd64
|
||||
# (build_one_codename skips an Architecture:all package on non-amd64; see the
|
||||
# control_binary_arch test below).
|
||||
for my $t (@targets) {
|
||||
for my $boot (qw(syslinux-xcat grub2-xcat elilo-xcat xnba-undi)) {
|
||||
for my $boot (qw(syslinux-xcat grub2-xcat elilo-xcat xnba-undi ipxe-xcat)) {
|
||||
ok(exists $m{$t}{$boot}, "$boot required-present on $t (arch:all, verified on every arch)");
|
||||
}
|
||||
}
|
||||
@@ -407,6 +373,17 @@ SKIP: {
|
||||
ok(!index_has_native_arch(undef, 'amd64'), 'undef index text -> not built (no crash)');
|
||||
}
|
||||
|
||||
# ipxe-xcat is built once on amd64 like the other boot components. Its control file must declare
|
||||
# Architecture: all, or every other arch would rebuild it.
|
||||
{
|
||||
open my $fh, '<', "$FindBin::Bin/../ipxe-xcat/debian/control" or die "ipxe-xcat/debian/control: $!";
|
||||
my $ctl = do { local $/; <$fh> };
|
||||
close $fh;
|
||||
is(control_binary_arch($ctl, 'ipxe-xcat'), 'all', 'ipxe-xcat is Architecture:all');
|
||||
ok(!skip_arch_all_on($ctl, 'ipxe-xcat', 'amd64'), 'ipxe-xcat is built on amd64');
|
||||
ok(skip_arch_all_on($ctl, 'ipxe-xcat', $_), "ipxe-xcat is not rebuilt on $_") for qw(ppc64el riscv64);
|
||||
}
|
||||
|
||||
# ---- control_binary_arch: PURE Architecture lookup for a specific BINARY package in debian/control --
|
||||
# Drives build_one_codename's "skip arch:all on non-amd64" (single-producer) decision. Must pick the
|
||||
# right binary paragraph -- e.g. the syslinux SOURCE is 'any' but the syslinux-xcat subpackage is 'all'.
|
||||
@@ -741,6 +718,7 @@ STUB
|
||||
'grub2-xcat' => 'grub2-xcat',
|
||||
'elilo-xcat' => 'elilo',
|
||||
'xnba-undi' => 'xnba',
|
||||
'ipxe-xcat' => 'ipxe-xcat',
|
||||
);
|
||||
my %manifest = read_manifest("$root/debs-manifest.conf");
|
||||
my %seen;
|
||||
|
||||
Reference in New Issue
Block a user