mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-09-30 14:55:16 +00:00
Merge remote-tracking branch 'upstream/master' into HEAD
# Conflicts: # docs/source/overview/support_matrix.rst
This commit is contained in:
@@ -7,7 +7,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install dependencies
|
||||
run: sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends --no-install-suggests build-essential fakeroot reprepro devscripts debhelper libcapture-tiny-perl libfile-slurper-perl libjson-perl libparallel-forkmanager-perl libsoap-lite-perl libdbi-perl libcgi-pm-perl quilt openssh-server dpkg looptools genometools software-properties-common
|
||||
run: sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends --no-install-suggests bats build-essential fakeroot reprepro devscripts debhelper libcapture-tiny-perl libfile-slurper-perl libjson-perl libparallel-forkmanager-perl libsoap-lite-perl libdbi-perl libcgi-pm-perl quilt openssh-server dpkg looptools genometools software-properties-common
|
||||
- name: Run tests
|
||||
run: perl github_action_xcat_test.pl
|
||||
|
||||
|
||||
@@ -147,16 +147,22 @@ use constant XCAT_PROBE_HELPERS => qw(
|
||||
ServiceNodeUtils.pm
|
||||
);
|
||||
|
||||
# Packages whose .deb carries a real architecture. Everything else in xcat-core is
|
||||
# Perl and ships as Architecture: all -- one binary serving every Ubuntu release and
|
||||
# every arch, which is why this build never needs a per-codename chroot.
|
||||
my %ARCH_PACKAGES = map { $_ => 1 } qw(xCAT xCATsn xCAT-genesis-scripts);
|
||||
# Packages whose .deb carries a real architecture, and the architectures each is built
|
||||
# for. Everything else in xcat-core is Perl and ships as Architecture: all -- one binary
|
||||
# serving every Ubuntu release and every arch, which is why this build never needs a
|
||||
# per-codename chroot. xCAT-genesis-scripts has no riscv64 control file: riscv64 Genesis
|
||||
# ships as an OpenEmbedded package.
|
||||
my %ARCH_PACKAGES = (
|
||||
'xCAT' => [qw(amd64 ppc64el riscv64)],
|
||||
'xCATsn' => [qw(amd64 ppc64el riscv64)],
|
||||
'xCAT-genesis-scripts' => [qw(amd64 ppc64el)],
|
||||
);
|
||||
|
||||
# Ubuntu releases predating ppc64el. Kept as data rather than an `if` in the caller so
|
||||
# the repo-assembly and the package-selection paths cannot disagree about it.
|
||||
my %NO_PPC64EL = map { $_ => 1 } qw(saucy);
|
||||
|
||||
my @DEB_ARCHES = qw(amd64 ppc64el);
|
||||
my @DEB_ARCHES = qw(amd64 ppc64el riscv64);
|
||||
|
||||
# The Ubuntu releases the apt repository serves by default. Single source of truth:
|
||||
# the builder, the repo assembly and the tests all read it here, so they cannot drift.
|
||||
@@ -323,8 +329,8 @@ sub stage_probe_helpers {
|
||||
# 'all' is a single arch-independent build; the three arch packages get one per arch.
|
||||
sub deb_package_arches {
|
||||
my ($package) = @_;
|
||||
return @DEB_ARCHES if $ARCH_PACKAGES{$package // ''};
|
||||
return ('all');
|
||||
my $arches = $ARCH_PACKAGES{ $package // '' };
|
||||
return $arches ? @{$arches} : ('all');
|
||||
}
|
||||
|
||||
# dist_arches: the architectures a release's apt repo declares.
|
||||
|
||||
+5
-5
@@ -317,11 +317,11 @@ sub write_repo_metadata {
|
||||
. /etc/lsb-release
|
||||
cd `dirname $0`
|
||||
host_arch=`uname -m`
|
||||
if [ "$host_arch" != "ppc64le" ];then
|
||||
host_arch="amd64"
|
||||
else
|
||||
host_arch="ppc64el"
|
||||
fi
|
||||
case "$host_arch" in
|
||||
ppc64le) host_arch="ppc64el" ;;
|
||||
riscv64) host_arch="riscv64" ;;
|
||||
*) host_arch="amd64" ;;
|
||||
esac
|
||||
echo deb [arch=$host_arch] file://"`pwd`" $DISTRIB_CODENAME main > /etc/apt/sources.list.d/xcat-core.list
|
||||
SCRIPT
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ In a homogeneous cluster, the management node is the same hardware architecture
|
||||
|
||||
The issues arises in a heterogeneous cluster, where the management node is running a different level operating system *or* hardware architecture as the compute nodes in which to deploy the image. The ``genimage`` command that builds stateless images depends on various utilities provided by the base operating system and needs to be run on a node with the same hardware architecture and *major* Operating System release as the nodes that will be booted from the image.
|
||||
|
||||
When running xCAT >= 2.17 on EL >= 8 based management node with x86_64 architecture, qemu-user-static can be used to cross-build ppc64*, aarch64 and riscv64 osimages. Therefore, you don't need to build images on systems with the target architecture anymore.
|
||||
When running xCAT >= 2.17 on EL >= 8 based management node with x86_64 architecture, qemu-user-static can be used to cross-build ppc64*, aarch64 and riscv64 osimages. Therefore, you don't need to build images on systems with the target architecture anymore. The same applies to an Ubuntu management node, where ``qemu-user-static`` and ``binfmt-support`` are packages of the distribution and register the handler on install.
|
||||
|
||||
Cross-build ppc64*/aarch64/riscv64 stateless/statelite image on x86_64 management node
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
Support Matrix
|
||||
==============
|
||||
|
||||
+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
| | RHEL | SLES | RHEL | SLES | Ubuntu | RHEL | SLES | Ubuntu | RHEL | SLES | Ubuntu | RHEL |
|
||||
| | ppc64 | ppc64 | x86_64 | x86_64 | x86_64 | ppc64le | ppc64le | ppc64el | aarch64 | aarch64 | aarch64 | riscv64 |
|
||||
| | CN | CN | CN | CN | CN | CN | CN | CN | CN | CN | CN | CN |
|
||||
+=========+=========+=========+=========+=========+=========+=========+=========+=========+=========+=========+=========+=========+
|
||||
| RHEL | | | | | | | | | | | | |
|
||||
| ppc64 | yes | yes | yes | yes | yes | yes | yes | yes | no | no | no | no |
|
||||
| MN/SN | | | [1]_ | [1]_ | [1]_ | | | | | | | |
|
||||
+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
| SLES | | | | | | | | | | | | |
|
||||
| ppc64 | yes | yes | yes | yes | yes | yes | yes | yes | no | no | no | no |
|
||||
| MN/SN | | | [1]_ | [1]_ | [1]_ | | | | | | | |
|
||||
+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
| RHEL | | | | | | | | | | | | |
|
||||
| x86_64 | yes | yes | yes | yes | yes | yes | yes | yes | yes | no | no | yes |
|
||||
| MN/SN | [4]_ | [4]_ | | | | | | | | | | [6]_ |
|
||||
+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
| SLES | | | | | | | | | | | | |
|
||||
| x86_64 | yes | yes | yes | yes | yes | yes | yes | yes | yes | no | no | no |
|
||||
| MN/SN | [4]_ | [4]_ | | | | | | | | | | |
|
||||
+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
| Ubuntu | | | | | | | | | | | | |
|
||||
| x86_64 | yes | yes | yes | yes | yes | yes | yes | yes | yes | no | no | no |
|
||||
| MN/SN | [5]_ | [5]_ | | | | | | | | | | |
|
||||
+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
| RHEL | | | | | | | | | | | | |
|
||||
| ppc64le | yes | yes | yes | yes | yes | yes | yes | yes | no | no | no | no |
|
||||
| MN/SN | [2]_ | [2]_ | | | | | | | | | | |
|
||||
+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
| SLES | | | | | | | | | | | | |
|
||||
| ppc64le | no | no | yes | yes | yes | yes | yes | yes | no | no | no | no |
|
||||
| MN/SN | | | | | | | | | | | | |
|
||||
+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
| Ubuntu | | | | | | | | | | | | |
|
||||
| ppc64el | yes | yes | yes | yes | yes | yes | yes | yes | no | no | no | no |
|
||||
| MN/SN | [3]_ | [3]_ | | | | | | | | | | |
|
||||
+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
| | RHEL | SLES | RHEL | SLES | Ubuntu | RHEL | SLES | Ubuntu | RHEL | SLES | Ubuntu | RHEL | Ubuntu |
|
||||
| | ppc64 | ppc64 | x86_64 | x86_64 | x86_64 | ppc64le | ppc64le | ppc64el | aarch64 | aarch64 | aarch64 | riscv64 | riscv64 |
|
||||
| | CN | CN | CN | CN | CN | CN | CN | CN | CN | CN | CN | CN | CN |
|
||||
+=========+=========+=========+=========+=========+=========+=========+=========+=========+=========+=========+=========+=========+=========+
|
||||
| RHEL | | | | | | | | | | | | | |
|
||||
| ppc64 | yes | yes | yes | yes | yes | yes | yes | yes | no | no | no | no | no |
|
||||
| MN/SN | | | [1]_ | [1]_ | [1]_ | | | | | | | | |
|
||||
+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
| SLES | | | | | | | | | | | | | |
|
||||
| ppc64 | yes | yes | yes | yes | yes | yes | yes | yes | no | no | no | no | no |
|
||||
| MN/SN | | | [1]_ | [1]_ | [1]_ | | | | | | | | |
|
||||
+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
| RHEL | | | | | | | | | | | | | |
|
||||
| x86_64 | yes | yes | yes | yes | yes | yes | yes | yes | yes | no | no | yes | no |
|
||||
| MN/SN | [4]_ | [4]_ | | | | | | | | | | [6]_ | |
|
||||
+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
| SLES | | | | | | | | | | | | | |
|
||||
| x86_64 | yes | yes | yes | yes | yes | yes | yes | yes | yes | no | no | no | no |
|
||||
| MN/SN | [4]_ | [4]_ | | | | | | | | | | | |
|
||||
+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
| Ubuntu | | | | | | | | | | | | | |
|
||||
| x86_64 | yes | yes | yes | yes | yes | yes | yes | yes | yes | no | no | no | yes |
|
||||
| MN/SN | [5]_ | [5]_ | | | | | | | | | | | [7]_ |
|
||||
+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
| RHEL | | | | | | | | | | | | | |
|
||||
| ppc64le | yes | yes | yes | yes | yes | yes | yes | yes | no | no | no | no | no |
|
||||
| MN/SN | [2]_ | [2]_ | | | | | | | | | | | |
|
||||
+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
| SLES | | | | | | | | | | | | | |
|
||||
| ppc64le | no | no | yes | yes | yes | yes | yes | yes | no | no | no | no | no |
|
||||
| MN/SN | | | | | | | | | | | | | |
|
||||
+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
| Ubuntu | | | | | | | | | | | | | |
|
||||
| ppc64el | yes | yes | yes | yes | yes | yes | yes | yes | no | no | no | no | no |
|
||||
| MN/SN | [3]_ | [3]_ | | | | | | | | | | | |
|
||||
+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
|
||||
|
||||
Notes:
|
||||
@@ -54,3 +54,4 @@ Notes:
|
||||
.. [4] If the compute nodes are DFM managed systems, will need the ppc64le DFM and ppc64le hardware server on the management node.
|
||||
.. [5] Does not support DFM managed compute nodes, hardware control does not work.
|
||||
.. [6] riscv64 compute nodes boot through UEFI firmware and grub2 only. The management node needs the riscv64 Genesis image (``xCAT-genesis-openembedded-riscv64``) and ``/tftpboot/boot/grub2/grub2.riscv64`` (see :doc:`/guides/install-guides/yum/grub2`). EL10 compute nodes are supported, validated from an EL10 x86_64 management node; a riscv64 management node is documented in :doc:`/guides/admin-guides/manage_clusters/riscv64/index`.
|
||||
.. [7] Ubuntu 24.04 and 26.04 riscv64 compute nodes, stateful and stateless, validated from an Ubuntu x86_64 management node. The riscv64 packages that carry a binary (``goconserver``, ``ipmitool-xcat``, ``conserver-xcat``) come from a riscv64 xcat-dep apt repository; see :doc:`/guides/admin-guides/manage_clusters/riscv64/index`.
|
||||
|
||||
@@ -60,7 +60,10 @@ the repository declares. Only ``xCAT``, ``xCATsn`` and ``xCAT-genesis-scripts``
|
||||
carry an architecture, and there the difference is packaging metadata rather than
|
||||
compiled output. That is why this build needs no ``sbuild`` and no per-codename
|
||||
chroot -- unlike xcat-deps, whose packages are compiled and genuinely differ per
|
||||
release.
|
||||
release. ``xCAT`` and ``xCATsn`` are built for riscv64 as well as amd64 and
|
||||
ppc64el, and every release the repository serves declares the architecture;
|
||||
``xCAT-genesis-scripts`` keeps the two architectures it has control files for,
|
||||
because riscv64 Genesis ships as an OpenEmbedded package instead.
|
||||
|
||||
Helpers shared by both builders live in ``build-utils/lib/XCAT/BuildUtils.pm``.
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ Key Attributes
|
||||
+--------------------------+----------------------+-----------------------------------+
|
||||
| aarch64 | >=el8 | grub2 |
|
||||
+--------------------------+----------------------+-----------------------------------+
|
||||
| riscv64 | >=el10 | grub2,grub2-http,grub2-tftp |
|
||||
| riscv64 | >=el10, >=ubuntu24.04| grub2,grub2-http,grub2-tftp |
|
||||
+--------------------------+----------------------+-----------------------------------+
|
||||
|
||||
* postscripts:
|
||||
|
||||
+2
@@ -6,6 +6,8 @@ The name of the packages that will be installed on the node are stored in the pa
|
||||
* The package list file contains the names of the packages that comes from the os distro. They are stored in .pkglist file.
|
||||
* The other package list file contains the names of the packages that do NOT come from the os distro. They are stored in .otherpkgs.pkglist file.
|
||||
|
||||
On Ubuntu releases that install with Subiquity, the packages in the .pkglist file are installed during the autoinstall from the configured apt mirror and the pkgdir mirrors, without recommended packages as ``ospkgs`` installs them, and ``ospkgs`` applies the whole list again after the first boot. A version pin, a target release or an architecture qualifier in the list, a list that carries a ``#ENV:`` setting, and the list of an osimage with ``environvar`` are installed by ``ospkgs`` only. The apt sources the installer uses for the pkgdir mirrors and the otherpkgs repository do not remain on the node: ``ospkgs`` and ``otherpkgs`` write their own after the first boot, as before. ``ospkgs`` writes http mirrors only, so an https mirror or a local directory in pkgdir serves the autoinstall and is not an apt source after the first boot.
|
||||
|
||||
The path to the package lists will be read from the osimage definition. Which osimage a node is using is specified by the provmethod attribute. To display this value for a node: ::
|
||||
|
||||
lsdef node1 -i provmethod
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
RISC-V 64-bit (riscv64)
|
||||
=======================
|
||||
|
||||
xCAT manages RISC-V 64-bit (``riscv64``) compute nodes running EL10. Rocky
|
||||
Linux 10 is the reference distribution; the RHEL 10 RISC-V developer preview
|
||||
uses the same media layout. The general cluster management documentation under
|
||||
xCAT manages RISC-V 64-bit (``riscv64``) compute nodes running EL10 or Ubuntu.
|
||||
Rocky Linux 10 is the reference EL distribution and the RHEL 10 RISC-V developer
|
||||
preview uses the same media layout; on the Ubuntu side, 24.04 and 26.04 are
|
||||
supported from the live-server media. The general cluster management documentation under
|
||||
:doc:`/guides/admin-guides/manage_clusters/index` applies; this page
|
||||
only covers what is specific to the architecture.
|
||||
|
||||
@@ -21,16 +22,31 @@ What riscv64 nodes need
|
||||
``grub2-http`` is recommended for installers, whose initrd is large.
|
||||
* ``nodetype.arch`` and ``osimage.osarch`` are ``riscv64``. No alias is
|
||||
needed: ``uname -m``, rpm and dpkg all use the same token.
|
||||
* ``/tftpboot/boot/grub2/grub2.riscv64``: the EL grub2 UEFI image for riscv64
|
||||
(the ``EFI/BOOT/grubriscv64.efi`` of the EL10 riscv64 BaseOS tree).
|
||||
``copycds`` publishes it from the installation media when the management node
|
||||
does not have it yet, the ``grub2-xcat`` package installs the same image, and
|
||||
:doc:`/guides/install-guides/yum/grub2` describes copying it by hand. An image
|
||||
that is already there is never replaced.
|
||||
* ``/tftpboot/boot/grub2/grub2.riscv64``: the grub2 UEFI image the firmware
|
||||
loads. On EL media it is the ``EFI/BOOT/grubriscv64.efi`` of the riscv64
|
||||
BaseOS tree, which the ``grub2-xcat`` package also installs and
|
||||
:doc:`/guides/install-guides/yum/grub2` describes copying by hand; ``copycds``
|
||||
publishes it when the management node does not have it yet and keeps an image
|
||||
that is already there.
|
||||
|
||||
Ubuntu media are different: the loader they carry boots only from the media,
|
||||
because it holds a built-in configuration that searches for the live
|
||||
filesystem and never reads the configuration ``nodeset`` writes. ``copycds``
|
||||
therefore builds a netboot image from the ``grub-efi-riscv64-bin`` package on
|
||||
the media, with the network modules and the ``/boot/grub2`` prefix compiled
|
||||
in, and installs it under that name. An image already there is kept when it
|
||||
carries that prefix and those modules, which is what the loader this path
|
||||
builds looks like; anything else is replaced, because the image the media
|
||||
carry cannot reach the configuration. When the media cannot produce a
|
||||
replacement, whatever is there is left untouched and ``copycds`` says so:
|
||||
check that the nodes still boot, since the file was not built for this path.
|
||||
Building the loader needs ``grub-mkimage``, which ``grub-common`` provides and
|
||||
``xcat-server`` requires.
|
||||
* The riscv64 Genesis image (``xCAT-genesis-openembedded-riscv64``) for
|
||||
discovery, BMC setup and flashing. Its kernel is loaded by grub2 through the
|
||||
EFI stub. ``go-xcat`` installs the package; on a management node built another
|
||||
way, install it explicitly (``dnf install xCAT-genesis-openembedded-riscv64``),
|
||||
way, install it explicitly (``dnf install xCAT-genesis-openembedded-riscv64``,
|
||||
or ``apt install xcat-genesis-openembedded-riscv64`` on Ubuntu),
|
||||
the same way the images of other architectures are installed for a mixed
|
||||
cluster. ``xcatconfig`` runs ``mknb riscv64`` for every installed image.
|
||||
The management node itself is x86_64 (the validated combination, see
|
||||
@@ -65,6 +81,9 @@ for the other architectures.
|
||||
Stateful (diskful) installation
|
||||
--------------------------------
|
||||
|
||||
EL10
|
||||
~~~~
|
||||
|
||||
Import the Rocky Linux 10 riscv64 DVD with ``copycds``; it creates the
|
||||
``rocky10.x-riscv64-install-compute`` osimage. The installer kernel and initrd
|
||||
come from ``images/pxeboot`` on the media, like x86_64 and aarch64.
|
||||
@@ -84,6 +103,28 @@ package lists add ``grub2-efi-riscv64`` and ``efibootmgr``, and the
|
||||
these files. After ``nodeset <node> boot`` the firmware boots the installed
|
||||
system because the per-node ``grub2-<node>`` loader link is removed.
|
||||
|
||||
Ubuntu
|
||||
~~~~~~
|
||||
|
||||
Import the Ubuntu 24.04 or 26.04 riscv64 live-server ISO with ``copycds``; it
|
||||
creates the ``ubuntu<version>-riscv64-install-compute`` osimage. The installer
|
||||
kernel and initrd come from ``casper/vmlinux`` and ``casper/initrd``, where the
|
||||
riscv64 media keep them.
|
||||
|
||||
The installer needs no riscv64 accommodation of the kind EL10 requires: Subiquity
|
||||
installs ``grub-efi-riscv64`` itself, writes both ``\EFI\ubuntu\grubriscv64.efi``
|
||||
and the removable-media fallback ``\EFI\BOOT\BOOTRISCV64.EFI``, and registers the
|
||||
UEFI boot entry. The shared ``compute.subiquity.tmpl`` is used unchanged.
|
||||
|
||||
The autoinstall configuration is fetched from the management node over HTTP with
|
||||
``ds=nocloud-net``. That argument holds a semicolon, which grub2 reads as a
|
||||
command separator, so the boot loader configuration quotes it; a node whose
|
||||
kernel command line ends before the seed URL is a sign of an unquoted separator.
|
||||
|
||||
Packages the media do not carry are taken from ``ports.ubuntu.com``, which is
|
||||
where every architecture other than amd64 and i386 is published. Set
|
||||
``site.ubuntu_apt_mirror`` to point at a local mirror instead.
|
||||
|
||||
Crash dumps
|
||||
~~~~~~~~~~~
|
||||
|
||||
@@ -116,6 +157,15 @@ management node needs the riscv64 user-mode emulator registered with
|
||||
systemd-binfmt, as described in
|
||||
:doc:`/advanced/mixed_cluster/building_stateless_images`.
|
||||
|
||||
On Ubuntu, ``genimage`` builds the image with ``debootstrap`` from the
|
||||
``compute.ubuntu24.04.riscv64`` and ``compute.ubuntu26.04.riscv64`` package
|
||||
lists. It bootstraps from ``ports.ubuntu.com``, because ``archive.ubuntu.com``
|
||||
publishes amd64 and i386 only; ``site.ubuntu_apt_mirror`` overrides that for a
|
||||
local mirror serving every architecture. A management node of another
|
||||
architecture needs the same ``qemu-user-static`` binfmt registration as EL, and
|
||||
a release older than the one being built needs the target's ``debootstrap``
|
||||
script, which is a symlink to ``gutsy`` for every modern Ubuntu.
|
||||
|
||||
Management node on riscv64
|
||||
--------------------------
|
||||
|
||||
@@ -142,6 +192,15 @@ that disables weak dependencies (``install_weak_deps=False``) has to install
|
||||
``perl-DB_File`` explicitly to keep the Confluent client working.
|
||||
xCAT does not install anything from CPAN; every dependency is an rpm.
|
||||
|
||||
On Ubuntu the ``xcat`` and ``xcatsn`` packages are built for riscv64 and the
|
||||
apt repository indexes the architecture, so ``apt install xcat`` brings up a
|
||||
riscv64 management node. Everything else xCAT needs comes from the Ubuntu
|
||||
riscv64 archive, except the xcat-dep packages that carry a binary:
|
||||
``goconserver``, ``ipmitool-xcat`` and ``conserver-xcat`` must come from a
|
||||
riscv64 xcat-dep apt repository. The remaining xcat-dep packages, including
|
||||
``grub2-xcat`` and the x86-only boot loaders, are ``Architecture: all`` and
|
||||
install anywhere.
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
@@ -151,7 +210,9 @@ Limitations
|
||||
HTTP boot firmware yet, and a node that ``nodeset`` has configured is offered
|
||||
its per-node boot loader over TFTP, as on the other architectures, so keep PXE
|
||||
boot enabled in the firmware.
|
||||
* Ubuntu riscv64 is not supported yet.
|
||||
* Ubuntu 26.04 riscv64 requires the RVA23 profile. A machine that implements
|
||||
only the older profile stops with an illegal instruction early in userspace;
|
||||
24.04 runs on the older profile.
|
||||
* The serial console defaults to ``ttyS<site.defserialport>``; boards whose
|
||||
firmware exposes the console on another device need
|
||||
``linuximage.addkcmdline`` or the serial settings adjusted.
|
||||
|
||||
@@ -62,7 +62,7 @@ noderes Attributes:
|
||||
ppc64le NonVirtualize ALL petitboot
|
||||
ppc64le PowerKVM Guest ALL grub2,grub2-http,grub2-tftp
|
||||
aarch64 >=el8 grub2
|
||||
riscv64 >=el10 grub2,grub2-http,grub2-tftp
|
||||
riscv64 >=el10, >=ubuntu24.04 grub2,grub2-http,grub2-tftp
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -547,7 +547,7 @@ group Attributes:
|
||||
ppc64le NonVirtualize ALL petitboot
|
||||
ppc64le PowerKVM Guest ALL grub2,grub2-http,grub2-tftp
|
||||
aarch64 >=el8 grub2
|
||||
riscv64 >=el10 grub2,grub2-http,grub2-tftp
|
||||
riscv64 >=el10, >=ubuntu24.04 grub2,grub2-http,grub2-tftp
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -547,7 +547,7 @@ node Attributes:
|
||||
ppc64le NonVirtualize ALL petitboot
|
||||
ppc64le PowerKVM Guest ALL grub2,grub2-http,grub2-tftp
|
||||
aarch64 >=el8 grub2
|
||||
riscv64 >=el10 grub2,grub2-http,grub2-tftp
|
||||
riscv64 >=el10, >=ubuntu24.04 grub2,grub2-http,grub2-tftp
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ Differentiators
|
||||
|
||||
* Support Multiple Hardware
|
||||
|
||||
IBM Power, IBM Power LE, x86_64, aarch64 (alpha support), riscv64 (EL10, UEFI + grub2)
|
||||
IBM Power, IBM Power LE, x86_64, aarch64 (alpha support), riscv64 (EL10 and Ubuntu, UEFI + grub2)
|
||||
|
||||
* Support Multiple Virtualization Infrastructures
|
||||
|
||||
|
||||
@@ -24,3 +24,8 @@ Operating System & Hardware Support Matrix
|
||||
.. note::
|
||||
|
||||
EL stands for Enterprise Linux, such as: Red Hat Enterprise Linux (RHEL), Rocky Linux, AlmaLinux, CentOS and Oracle Linux.
|
||||
|
||||
riscv64 support covers EL 10 (Rocky Linux 10 and the RHEL 10 RISC-V developer preview), on nodes
|
||||
that boot through UEFI firmware and grub2. Both stateful and stateless nodes are supported, and
|
||||
the management node itself can run on riscv64. See
|
||||
:doc:`/guides/admin-guides/manage_clusters/riscv64/index` for details.
|
||||
|
||||
@@ -311,8 +311,11 @@ sub preserve_source_tree{
|
||||
return 1;
|
||||
}
|
||||
|
||||
@output = runcmd("ls $unitsrc/xCAT-test/unit/*.t | wc -l");
|
||||
print "[preserve_source_tree] preserved $srcdir in $unitsrc ($output[0] unit tests)\n";
|
||||
@output = runcmd("find $unitsrc/xCAT-test/unit -name '*.t' | wc -l");
|
||||
my $perl_count = $output[0];
|
||||
@output = runcmd("find $unitsrc/xCAT-test/bats -name '*.bats' 2>/dev/null | wc -l");
|
||||
my $bats_count = $output[0];
|
||||
print "[preserve_source_tree] preserved $srcdir in $unitsrc ($perl_count Perl unit tests, $bats_count BATS tests)\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -466,6 +469,39 @@ sub run_unit_tests{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#--------------------------------------------------------
|
||||
# Fuction name: run_bats_tests
|
||||
# Description: Run shell-script unit tests under xCAT-test/bats.
|
||||
# Runs against the pre-build copy of the source tree taken by
|
||||
# preserve_source_tree(), like the Perl unit tests.
|
||||
# Attributes:
|
||||
# Return code: 0 all tests passed, 1 otherwise
|
||||
#--------------------------------------------------------
|
||||
sub run_bats_tests{
|
||||
my $testdir = "$unitsrc/xCAT-test/bats";
|
||||
my @output = runcmd("find $testdir -name '*.bats' -print -quit 2>/dev/null");
|
||||
if (!@output) {
|
||||
print "[run_bats_tests] no BATS tests found under $testdir\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
my $cmd = "cd $unitsrc && bats -r xCAT-test/bats";
|
||||
print "[run_bats_tests] running $cmd\n";
|
||||
@output = runcmd("$cmd");
|
||||
print Dumper \@output;
|
||||
if($::RUNCMD_RC){
|
||||
print RED "[run_bats_tests] $cmd ....[Failed]\n";
|
||||
$check_result_str .= "> **BATS TESTS Failed** : Please click ``Details`` label in ``Merge pull request`` box for detailed information\n";
|
||||
print $check_result_str;
|
||||
return 1;
|
||||
}
|
||||
|
||||
print "[run_bats_tests] $cmd ....[Pass]\n";
|
||||
$check_result_str .= "> **BATS TESTS Successful**\n";
|
||||
print $check_result_str;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#--------------------------------------------------------
|
||||
# Fuction name: check_syntax
|
||||
# Description:
|
||||
@@ -689,6 +725,15 @@ if($rst){
|
||||
}
|
||||
mark_time("run_unit_tests");
|
||||
|
||||
#Run shell-script unit tests.
|
||||
print GREEN "\n------Running xCAT-test BATS tests ------\n";
|
||||
$rst = run_bats_tests();
|
||||
if($rst){
|
||||
print RED "Run of xCAT-test BATS tests failed\n";
|
||||
exit $rst;
|
||||
}
|
||||
mark_time("run_bats_tests");
|
||||
|
||||
#Check the syntax of changing code
|
||||
print GREEN "\n------ Checking the syntax of changed code------\n";
|
||||
$rst = check_syntax();
|
||||
|
||||
@@ -8,7 +8,7 @@ Homepage: https://xcat.org/
|
||||
|
||||
Package: perl-xcat
|
||||
Architecture: all
|
||||
Depends: ${perl:Depends}, libhtml-form-perl
|
||||
Depends: ${perl:Depends}, libhtml-form-perl, libxml-simple-perl, libxml-parser-perl, libio-socket-ssl-perl, libdbi-perl, libjson-perl, libwww-perl, libxml-libxml-perl, libexpect-perl, libsnmp-perl, libsocket6-perl, libio-socket-inet6-perl
|
||||
Description: xCAT perl libraries
|
||||
Provides perl xCAT libraries for core functionality. Required for all xCAT installations.
|
||||
Includes xCAT::Table, xCAT::NodeRange, among others.
|
||||
|
||||
@@ -676,7 +676,7 @@ passed as argument rather than by table value',
|
||||
ppc64le NonVirtualize ALL petitboot
|
||||
ppc64le PowerKVM Guest ALL grub2,grub2-http,grub2-tftp
|
||||
aarch64 >=el8 grub2
|
||||
riscv64 >=el10 grub2,grub2-http,grub2-tftp
|
||||
riscv64 >=el10, >=ubuntu24.04 grub2,grub2-http,grub2-tftp
|
||||
|
||||
',
|
||||
tftpserver => 'The TFTP server for this node (as known by this node). If not set, it defaults to networks.tftpserver.',
|
||||
|
||||
@@ -4876,6 +4876,7 @@ sub splitkcmdline {
|
||||
# without network drivers instead of stopping at debootstrap.
|
||||
my %DEBIAN_ARCH = (
|
||||
'x86_64' => 'amd64',
|
||||
'x86' => 'i386',
|
||||
);
|
||||
|
||||
sub debian_arch {
|
||||
@@ -4898,6 +4899,7 @@ my @XCAT_ARCH_FROM_DEBIAN = (
|
||||
[ qr/^ppc64el$/ => 'ppc64el' ],
|
||||
[ qr/ppc|powerpc/ => 'ppc64' ],
|
||||
[ qr/^amd64$/ => 'x86_64' ],
|
||||
[ qr/^riscv64$/ => 'riscv64' ],
|
||||
);
|
||||
|
||||
sub xcat_arch_from_debian {
|
||||
|
||||
@@ -7,8 +7,8 @@ Standards-Version: 3.9.4
|
||||
|
||||
Package: xcat-client
|
||||
Architecture: all
|
||||
Depends: ${perl:Depends}, perl-xcat (>= 2.13-snap000000000000)
|
||||
Recommends: libsort-versions-perl, nmap
|
||||
Depends: ${perl:Depends}, perl-xcat (>= 2.13-snap000000000000), libcapture-tiny-perl, nmap
|
||||
Recommends: libsort-versions-perl
|
||||
Description: Core executables and data of the xCAT management project
|
||||
xCAT-client provides the fundamental xCAT commands (chtab, chnode, rpower,
|
||||
etc) helpful in administrating systems at scale, with particular attention
|
||||
|
||||
@@ -9,14 +9,15 @@ depends() {
|
||||
}
|
||||
|
||||
installkernel() {
|
||||
local modules_dep modfile modname
|
||||
local modules_dep modules_root modfile modname
|
||||
|
||||
if [[ -n "${kernel:-}" && -r "/lib/modules/$kernel/modules.dep" ]]; then
|
||||
modules_dep="/lib/modules/$kernel/modules.dep"
|
||||
elif [[ -n "${KERNELVERSION:-}" && -r "/lib/modules/$KERNELVERSION/modules.dep" ]]; then
|
||||
modules_dep="/lib/modules/$KERNELVERSION/modules.dep"
|
||||
modules_root="${DRACUT_MODULES_ROOT:-/lib/modules}"
|
||||
if [[ -n "${kernel:-}" && -r "$modules_root/$kernel/modules.dep" ]]; then
|
||||
modules_dep="$modules_root/$kernel/modules.dep"
|
||||
elif [[ -n "${KERNELVERSION:-}" && -r "$modules_root/$KERNELVERSION/modules.dep" ]]; then
|
||||
modules_dep="$modules_root/$KERNELVERSION/modules.dep"
|
||||
else
|
||||
modules_dep=$(ls -1 /lib/modules/*/modules.dep 2>/dev/null | head -n 1)
|
||||
modules_dep=$(ls -1 "$modules_root"/*/modules.dep 2>/dev/null | head -n 1)
|
||||
fi
|
||||
|
||||
[[ -r "$modules_dep" ]] || return 0
|
||||
|
||||
@@ -9,14 +9,15 @@ depends() {
|
||||
}
|
||||
|
||||
installkernel() {
|
||||
local modules_dep modfile modname
|
||||
local modules_dep modules_root modfile modname
|
||||
|
||||
if [[ -n "${kernel:-}" && -r "/lib/modules/$kernel/modules.dep" ]]; then
|
||||
modules_dep="/lib/modules/$kernel/modules.dep"
|
||||
elif [[ -n "${KERNELVERSION:-}" && -r "/lib/modules/$KERNELVERSION/modules.dep" ]]; then
|
||||
modules_dep="/lib/modules/$KERNELVERSION/modules.dep"
|
||||
modules_root="${DRACUT_MODULES_ROOT:-/lib/modules}"
|
||||
if [[ -n "${kernel:-}" && -r "$modules_root/$kernel/modules.dep" ]]; then
|
||||
modules_dep="$modules_root/$kernel/modules.dep"
|
||||
elif [[ -n "${KERNELVERSION:-}" && -r "$modules_root/$KERNELVERSION/modules.dep" ]]; then
|
||||
modules_dep="$modules_root/$KERNELVERSION/modules.dep"
|
||||
else
|
||||
modules_dep=$(ls -1 /lib/modules/*/modules.dep 2>/dev/null | head -n 1)
|
||||
modules_dep=$(ls -1 "$modules_root"/*/modules.dep 2>/dev/null | head -n 1)
|
||||
fi
|
||||
|
||||
[[ -r "$modules_dep" ]] || return 0
|
||||
|
||||
@@ -7,6 +7,8 @@ use lib "$::XCATROOT/probe/lib/perl";
|
||||
use probe_utils;
|
||||
use xCAT::CommandUtils;
|
||||
use File::Basename;
|
||||
use File::Temp qw(tempfile);
|
||||
use POSIX qw(_exit sigprocmask WNOHANG SIG_BLOCK SIG_SETMASK SIGINT SIGTERM);
|
||||
use IO::Socket::INET;
|
||||
use Time::HiRes qw(gettimeofday sleep);
|
||||
use Getopt::Long;
|
||||
@@ -17,7 +19,6 @@ my $program_name = basename("$0");
|
||||
my $output = "stdout";
|
||||
my $duration = 10;
|
||||
my $test = 0;
|
||||
my $dumpfile = "/tmp/dhcpdumpfile.log";
|
||||
my $nic;
|
||||
|
||||
$::USAGE = "Usage:
|
||||
@@ -153,44 +154,57 @@ my $package = packdhcppkg($MAC);
|
||||
probe_utils->send_msg("$output", "i", "Start to detect DHCP, please wait $duration seconds");
|
||||
|
||||
$msg = "fork a process to capture the packet by tcpdump";
|
||||
my ($dumpfh, $dumpfile) = tempfile("detect_dhcpd.XXXXXX", TMPDIR => 1, UNLINK => 1);
|
||||
close($dumpfh);
|
||||
# INT and TERM stay blocked from the fork until the handler that stops the capture is in place.
|
||||
my $stop_signals = POSIX::SigSet->new(SIGINT, SIGTERM);
|
||||
my $signal_mask = POSIX::SigSet->new();
|
||||
sigprocmask(SIG_BLOCK, $stop_signals, $signal_mask);
|
||||
my $pid = fork;
|
||||
if (!defined $pid) {
|
||||
sigprocmask(SIG_SETMASK, $signal_mask);
|
||||
probe_utils->send_msg("$output", "f", $msg);
|
||||
exit 1;
|
||||
} elsif ($pid == 0) {
|
||||
|
||||
# Child process
|
||||
my $cmd = "$tcpdump -i $nic port 68 -n -vvvvvv > $dumpfile 2>/dev/null";
|
||||
`$cmd`;
|
||||
exit 0;
|
||||
# Child process: tcpdump itself, so the parent owns exactly this pid.
|
||||
sigprocmask(SIG_SETMASK, $signal_mask);
|
||||
open(STDOUT, '>', $dumpfile) or _exit(1);
|
||||
open(STDERR, '>', '/dev/null');
|
||||
exec($tcpdump, '-i', $nic, 'port', '68', '-n', '-vvvvvv');
|
||||
_exit(1);
|
||||
}
|
||||
$SIG{INT} = $SIG{TERM} = sub { kill_child(); exit 1; };
|
||||
sigprocmask(SIG_SETMASK, $signal_mask);
|
||||
probe_utils->send_msg("$output", "d", "The id of process which is used to capture the packet by tcpdump is $pid") if ($::VERBOSE);
|
||||
|
||||
my $start = Time::HiRes::gettimeofday();
|
||||
$start =~ s/(\d.*)\.(\d.*)/$1/;
|
||||
my $end = $start;
|
||||
while ($end - $start <= $duration) {
|
||||
$sock->send($package);
|
||||
probe_utils->send_msg("$output", "d", "Send DHCP rquest result: $@") if ($::VERBOSE && $@);
|
||||
unless ($sock->send($package)) {
|
||||
probe_utils->send_msg("$output", "d", "Send DHCP discover error: $!") if ($::VERBOSE);
|
||||
probe_utils->send_msg("$output", "f", "Send out DHCP discover");
|
||||
kill_child();
|
||||
exit 1;
|
||||
}
|
||||
sleep 2;
|
||||
$end = Time::HiRes::gettimeofday();
|
||||
$end =~ s/(\d.*)\.(\d.*)/$1/;
|
||||
}
|
||||
|
||||
$msg = "Kill the process which is used to capture the packet by tcpdump";
|
||||
kill_child();
|
||||
waitpid($pid, 0);
|
||||
sleep 1;
|
||||
`ps aux|grep -v grep |grep $pid > /dev/null 2>&1`;
|
||||
if (!$?) {
|
||||
$msg = "Capture the packets by tcpdump";
|
||||
my $capture_problem = kill_child();
|
||||
if ($capture_problem) {
|
||||
probe_utils->send_msg("$output", "d", "tcpdump $capture_problem") if ($::VERBOSE);
|
||||
probe_utils->send_msg("$output", "f", $msg);
|
||||
exit 1;
|
||||
}
|
||||
|
||||
$msg = "Dump test result";
|
||||
unless (open(FILE, "<$dumpfile")) {
|
||||
probe_utils->send_msg("$output", "d", "Open dump file $dumpfile failed") if ($::VERBOSE);
|
||||
probe_utils->send_msg("$output", "f", $msg);
|
||||
`rm -f $dumpfile` if (-e "$dumpfile");
|
||||
exit 1;
|
||||
}
|
||||
my %output;
|
||||
@@ -273,7 +287,6 @@ if (scalar(@server)) {
|
||||
}
|
||||
}
|
||||
|
||||
`rm -f $dumpfile` if (-e "$dumpfile");
|
||||
exit 0;
|
||||
|
||||
|
||||
@@ -379,11 +392,39 @@ sub packdhcppkg {
|
||||
return $package;
|
||||
}
|
||||
|
||||
# Stop the capture and reap it, once: INT and TERM are blocked while the pid is taken and until
|
||||
# tcpdump is reaped, so an interrupt in that window cannot orphan it or reach a recycled pid. The
|
||||
# handlers stay installed, so a later signal still leaves through exit and the END cleanup.
|
||||
# Returns '' when tcpdump ran until this TERM and stopped cleanly, otherwise what went wrong: it
|
||||
# ended on its own, ignored TERM and was killed, or left on another signal or with a non-zero
|
||||
# status.
|
||||
sub kill_child {
|
||||
kill 15, $pid;
|
||||
my @pidoftcpdump = `ps -ef | grep -E "[0-9]+:[0-9]+:[0-9]+ $tcpdump -i $nic" | awk -F' ' '{print \$2}'`;
|
||||
foreach my $cpid (@pidoftcpdump) {
|
||||
kill 15, $cpid;
|
||||
sigprocmask(SIG_BLOCK, $stop_signals);
|
||||
my $child = $pid;
|
||||
$pid = undef;
|
||||
unless ($child) {
|
||||
sigprocmask(SIG_SETMASK, $signal_mask);
|
||||
return '';
|
||||
}
|
||||
probe_utils->send_msg("$output", "d", "Kill process $pid used to capture the packet by 'tcpdump'") if ($::VERBOSE);
|
||||
my $reaped = waitpid($child, WNOHANG);
|
||||
my $early = ($reaped == $child) ? 1 : 0;
|
||||
if ($reaped == 0) {
|
||||
kill 'TERM', $child;
|
||||
foreach (1 .. 50) {
|
||||
last if ($reaped = waitpid($child, WNOHANG)) != 0;
|
||||
select(undef, undef, undef, 0.1);
|
||||
}
|
||||
if ($reaped == 0) {
|
||||
kill 'KILL', $child;
|
||||
$reaped = waitpid($child, 0);
|
||||
}
|
||||
}
|
||||
sigprocmask(SIG_SETMASK, $signal_mask);
|
||||
return "could not be reaped" if $reaped != $child;
|
||||
my ($signal, $status) = ($? & 127, $? >> 8);
|
||||
probe_utils->send_msg("$output", "d", "Kill process $child used to capture the packet by 'tcpdump'") if ($::VERBOSE);
|
||||
my $how = $signal ? "on signal $signal" : "with status $status";
|
||||
return "ended before the capture window did, $how" if $early;
|
||||
return '' if $signal == 15 || (!$signal && !$status);
|
||||
return "left the capture $how";
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ Homepage: https://xcat.org/
|
||||
|
||||
Package: xcat-server
|
||||
Architecture: all
|
||||
Depends: ${perl:Depends}, grub2-xcat (>= 2.02-0.76.el7.1.snap201905160255), perl-xcat (>= 2.13-snap000000000000), xcat-client (>= 2.13-snap000000000000), libsys-syslog-perl, libio-socket-ssl-perl, libxml-simple-perl, make, ucf, libdbd-sqlite3-perl, libexpect-perl, libnet-dns-perl, libsoap-lite-perl, libxml-libxml-perl, libsnmp-perl, debootstrap, libdigest-sha-perl,libcrypt-rijndael-perl,libcrypt-cbc-perl,libjson-perl, libnet-https-nb-perl, libhttp-async-perl
|
||||
Depends: ${perl:Depends}, grub-common, libcgi-pm-perl, grub2-xcat (>= 2.02-0.76.el7.1.snap201905160255), perl-xcat (>= 2.13-snap000000000000), xcat-client (>= 2.13-snap000000000000), libsys-syslog-perl, libio-socket-ssl-perl, libxml-simple-perl, make, ucf, libdbd-sqlite3-perl, libexpect-perl, libnet-dns-perl, libsoap-lite-perl, libxml-libxml-perl, libsnmp-perl, debootstrap, libdigest-sha-perl,libcrypt-rijndael-perl,libcrypt-cbc-perl,libjson-perl, libnet-https-nb-perl, libhttp-async-perl
|
||||
Description: Server and configuration utilities of xCAT
|
||||
xCAT-server provides the core server and configuration management components
|
||||
of xCAT.
|
||||
|
||||
@@ -1889,6 +1889,68 @@ sub get_pkglist_tex
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
|
||||
=head3 get_pkglist_records
|
||||
|
||||
The records of one or more pkglist files, one per line kept whole and
|
||||
includes followed. get_pkglist_tex joins records with commas, so it
|
||||
cannot separate a record that itself contains a comma.
|
||||
Arguments: comma-separated pkglist file names
|
||||
Returns: list of records
|
||||
=cut
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
sub get_pkglist_records
|
||||
{
|
||||
my $allfiles_pkglist = shift;
|
||||
if ($allfiles_pkglist =~ "xCAT::") {
|
||||
$allfiles_pkglist = shift;
|
||||
}
|
||||
my @records;
|
||||
foreach my $pkglist (split(/,/, $allfiles_pkglist // ''))
|
||||
{
|
||||
next if $pkglist eq '';
|
||||
push(@records, pkglist_file_records($pkglist, dirname($pkglist), 0));
|
||||
}
|
||||
return @records;
|
||||
}
|
||||
|
||||
# pkglist_file_records: the records of one pkglist file, read as get_pkglist_tex reads them, with an
|
||||
# #INCLUDE: record replaced by the records of the named file.
|
||||
# A nested include resolves against the directory of the listed pkglist, as get_pkglist_tex resolves it.
|
||||
sub pkglist_file_records
|
||||
{
|
||||
my ($file, $idir, $depth) = @_;
|
||||
my @records;
|
||||
open(my $fh, '<', $file) or return ("#INCLUDEBAD:cannot open pkglist file $file#");
|
||||
while (my $line = <$fh>)
|
||||
{
|
||||
chomp($line);
|
||||
$line =~ s/\s+$//;
|
||||
$line =~ s/^\s*//;
|
||||
next if $line eq '';
|
||||
next
|
||||
if ($line =~ /^#/
|
||||
&& $line !~ /^#INCLUDE:[^#^\n]+#/
|
||||
&& $line !~ /^#NEW_INSTALL_LIST#/
|
||||
&& $line !~ /^#ENV:[^#^\n]+#/);
|
||||
if ($line =~ /^#INCLUDE:([^#^\n]+)#(.*)$/ && $depth < 20)
|
||||
{
|
||||
my ($name, $note) = ($1, $2);
|
||||
my $include = xCAT::Utils->varsubinline($name, \%ENV);
|
||||
$include = "$idir/$include" unless $include =~ m{^/};
|
||||
my @included = pkglist_file_records($include, $idir, $depth + 1);
|
||||
$included[-1] .= $note if @included && $note ne '';
|
||||
push(@records, @included);
|
||||
next;
|
||||
}
|
||||
push(@records, $line);
|
||||
}
|
||||
close($fh);
|
||||
return @records;
|
||||
}
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
|
||||
=head3 includefile
|
||||
|
||||
handles #INCLUDE# in otherpkg.pkglist file
|
||||
|
||||
@@ -173,6 +173,11 @@ sub subvars {
|
||||
$inc =~ s/#INCLUDE_DEFAULT_RMPKGLIST_S#/#INCLUDE_RMPKGLIST:$pkglistfile#/g;
|
||||
}
|
||||
|
||||
# osimage environvar reaches apt-get through ospkgs alone, so such an image installs its list there,
|
||||
# and its pkgdir mirrors, which may need those variables too, stay out of the installer's sources
|
||||
my $environvar_set = ( $namedargs{environvar} // '' ) =~ /\S/;
|
||||
my $installer_pkgdirs = $environvar_set ? undef : $namedargs{pkgdirs};
|
||||
my @autoinstall;
|
||||
if (("ubuntu" eq $platform) || ("debian" eq $platform)) {
|
||||
|
||||
# since debian/ubuntu uses a preseed file instead of a kickstart file, pkglist
|
||||
@@ -186,6 +191,7 @@ sub subvars {
|
||||
if ($allpkglist =~ /#INCLUDEBAD:(.*)#/) {
|
||||
return "$1";
|
||||
}
|
||||
@autoinstall = ubuntu_autoinstall_packages( xCAT::Postage->get_pkglist_records($pkglistfile) ) unless $environvar_set;
|
||||
$allpkglist =~ s/,/ /g;
|
||||
$inc =~ s/#INCLUDE_DEFAULT_PKGLIST_PRESEED#/$allpkglist/g;
|
||||
|
||||
@@ -364,7 +370,7 @@ sub subvars {
|
||||
$inc =~ s/#INSTALL_SOURCES_IN_PRE#/$source_in_pre/g;
|
||||
if (("ubuntu" eq $platform) || ("debian" eq $platform)) {
|
||||
$inc =~ s/#INCLUDE_OSIMAGE_PKGDIR#/$pkgdirs[-1]/;
|
||||
$inc =~ s/#UBUNTU_SUBIQUITY_APT_CONFIG#/ubuntu_subiquity_apt_config($media_dir)/eg;
|
||||
$inc =~ s/#UBUNTU_SUBIQUITY_APT_CONFIG#/ubuntu_subiquity_apt_config($media_dir, $namedargs{osarch}, $installer_pkgdirs)/eg;
|
||||
}
|
||||
$inc =~ s/#WRITEREPO#/$writerepo/g;
|
||||
}
|
||||
@@ -377,7 +383,9 @@ sub subvars {
|
||||
$inc =~ s/#INCLUDE_NOP:([^#^\n]+)#/includefile($1,1,0)/eg;
|
||||
$inc =~ s/#XCATVAR:([^#]+)#/envvar($1)/eg;
|
||||
$inc =~ s/#ENV:([^#]+)#/envvar($1)/eg;
|
||||
$inc =~ s/#UBUNTU_SUBIQUITY_APT_CONFIG#/ubuntu_subiquity_apt_config($media_dir)/eg;
|
||||
$inc =~ s/#UBUNTU_SUBIQUITY_APT_CONFIG#/ubuntu_subiquity_apt_config($media_dir, $namedargs{osarch}, $installer_pkgdirs)/eg;
|
||||
# in the include pass, so a template that includes the stock Subiquity one gets its list as well
|
||||
$inc =~ s/^((?:[ \t]*- [^\n]*\n)*)([ \t]*)- #INCLUDE_DEFAULT_PKGLIST_AUTOINSTALL#[ \t]*\n/$1 . ubuntu_autoinstall_items($2, $1, \@autoinstall)/meg;
|
||||
$inc =~ s/#SUBIQUITYINSTALLNIC#/subiquity_install_nic()/eg;
|
||||
$inc =~ s/#SUBIQUITYINSTALLMAC#/subiquity_install_mac()/eg;
|
||||
$inc =~ s/#MACHINEPASSWORD#/machinepassword()/eg;
|
||||
@@ -1764,26 +1772,103 @@ sub subiquity_install_mac {
|
||||
return $macaddress;
|
||||
}
|
||||
|
||||
# ubuntu_autoinstall_packages: the packages of the pkglist records (whole lines, as
|
||||
# get_pkglist_records returns them) that a Subiquity autoinstall can install through its packages
|
||||
# list. A record holds one or more space-separated packages, as the preseed path reads it, each a
|
||||
# plain name or a task. A version pin or a target release stays with ospkgs, because the installer
|
||||
# runs apt-get without --allow-downgrades and a pin can require one, and so does a name with an
|
||||
# architecture qualifier, because a foreign architecture is enabled by a postscript that runs later. A preseed directive, told by its question type, a record that begins with a removal
|
||||
# or a group, which ospkgs removes or installs whole, a removal written with a trailing hyphen as
|
||||
# apt-get reads it, or a marker has
|
||||
# no autoinstall form, a comment ends the packages of a record, and a record with a token that is
|
||||
# none of these is left out whole. A list
|
||||
# that carries a #ENV: setting, which only ospkgs can pass to apt-get, or an unreadable include is
|
||||
# left to ospkgs whole; ospkgs still applies the whole list after the install.
|
||||
my %PRESEED_TYPE = map { $_ => 1 } qw(string boolean select multiselect note password text seen title error);
|
||||
|
||||
sub ubuntu_autoinstall_packages
|
||||
{
|
||||
my @records = grep { defined } @_;
|
||||
return () if grep { /#(?:ENV:|INCLUDEBAD:)/ } @records;
|
||||
my (@packages, %seen);
|
||||
RECORD: foreach my $record (@records) {
|
||||
my @tokens = grep { length } split( /\s+/, $record );
|
||||
next unless @tokens;
|
||||
next if @tokens >= 3 && $PRESEED_TYPE{ $tokens[2] };
|
||||
next if $tokens[0] =~ /^[-@]/; # ospkgs removes or installs the whole record
|
||||
my @found;
|
||||
foreach my $token (@tokens) {
|
||||
last if $token =~ /^#/;
|
||||
next if $token =~ /^[-@]/ || $token =~ /-$/;
|
||||
next RECORD unless $token =~ m{^[a-z0-9][a-z0-9+.-]*(?::[a-z0-9-]+)?(?:[=/][^\s/=]+|\^)?$};
|
||||
next if $token =~ m{[:=/]};
|
||||
push @found, $token;
|
||||
}
|
||||
push @packages, grep { !$seen{$_}++ } @found;
|
||||
}
|
||||
return @packages;
|
||||
}
|
||||
|
||||
# ubuntu_autoinstall_items: the list items for the pkglist packages at the token's indentation,
|
||||
# leaving out packages the items above the token already name, each quoted so a name such as null
|
||||
# or true stays a string. The time daemons exclude each
|
||||
# other, so when the template names one, the pkglist's stay with ospkgs, as they did before.
|
||||
my %UBUNTU_TIME_DAEMON = map { $_ => 1 } qw(chrony ntp ntpsec ntpdate ntpsec-ntpdate openntpd systemd-timesyncd);
|
||||
|
||||
sub ubuntu_autoinstall_items
|
||||
{
|
||||
my ($indent, $listed, $packages) = @_;
|
||||
my %named = map { $_ => 1 } ( $listed =~ /^[ \t]*- (\S+)[ \t]*$/mg );
|
||||
my $fixed_time_daemon = grep { $UBUNTU_TIME_DAEMON{$_} } keys %named;
|
||||
my @items = grep { !$named{$_} } @$packages;
|
||||
@items = grep { !$UBUNTU_TIME_DAEMON{ (split /[:=\/^]/, $_)[0] } } @items if $fixed_time_daemon;
|
||||
return join( '', map { "$indent- \"$_\"\n" } @items );
|
||||
}
|
||||
|
||||
sub ubuntu_subiquity_apt_mirror
|
||||
{
|
||||
my ($osarch) = @_;
|
||||
|
||||
# Apt mirror for Subiquity installs. site.ubuntu_apt_mirror overrides; otherwise default to the
|
||||
# public archive. The minimal live-server install media is not a complete package source, so a
|
||||
# real mirror is always required -- set site.ubuntu_apt_mirror to a local full mirror for
|
||||
# airgapped clusters (or to a geo/ports mirror as needed).
|
||||
my $default = 'http://archive.ubuntu.com/ubuntu';
|
||||
#
|
||||
# archive.ubuntu.com publishes amd64 and i386 only. Every other architecture, ppc64el and
|
||||
# riscv64 included, is on the ports archive. The osimage's architecture decides it, because
|
||||
# pkgdir is whatever path the administrator configured.
|
||||
my $default = (!$osarch || $osarch =~ /^(?:amd64|i386|x86|x86_64)$/)
|
||||
? 'http://archive.ubuntu.com/ubuntu'
|
||||
: 'http://ports.ubuntu.com/ubuntu-ports';
|
||||
|
||||
my $site_tab = xCAT::Table->new('site');
|
||||
return $default unless $site_tab;
|
||||
my $ent = $site_tab->getAttribs({ key => 'ubuntu_apt_mirror' }, 'value');
|
||||
return ($ent && defined($ent->{value}) && length($ent->{value})) ? $ent->{value} : $default;
|
||||
}
|
||||
|
||||
# The key curtin names in the Deb822 source it writes for the primary apt mirror on 24.04 and later.
|
||||
my $UBUNTU_ARCHIVE_KEYRING = '/usr/share/keyrings/ubuntu-archive-keyring.gpg';
|
||||
|
||||
sub ubuntu_subiquity_apt_config
|
||||
{
|
||||
my ($media_dir) = @_;
|
||||
my ($media_dir, $osarch, $pkgdirs) = @_;
|
||||
my $use_deb822 = ubuntu_subiquity_uses_deb822_sources($media_dir);
|
||||
my @otherpkg_sources = ubuntu_subiquity_otherpkg_sources();
|
||||
my $online_mirror = ubuntu_subiquity_apt_mirror($osarch);
|
||||
my $mirror_key = $use_deb822 ? $UBUNTU_ARCHIVE_KEYRING : '';
|
||||
my @otherpkg_sources = map { ubuntu_subiquity_otherpkg_source_spec( $_, $mirror_key, $online_mirror ) } ubuntu_subiquity_otherpkg_sources();
|
||||
my @pkgdir_sources = ubuntu_subiquity_pkgdir_source_specs( $pkgdirs, $mirror_key, $online_mirror );
|
||||
|
||||
# apt rejects two sources for one repository whose options differ, so a pkgdir entry that repeats an
|
||||
# otherpkgs repository adds its components to that source instead
|
||||
my %otherpkg_by_key = map { ( my $uri = $_->{uri} ) =~ s{/+$}{}; ( "$uri $_->{suites}" => $_ ) } @otherpkg_sources;
|
||||
@pkgdir_sources = grep {
|
||||
( my $uri = $_->{uri} ) =~ s{/+$}{};
|
||||
my $other = $otherpkg_by_key{"$uri $_->{suites}"};
|
||||
ubuntu_subiquity_add_components( $other, $_->{components} ) if $other;
|
||||
!$other;
|
||||
} @pkgdir_sources;
|
||||
|
||||
my $online_mirror = ubuntu_subiquity_apt_mirror();
|
||||
if ($online_mirror) {
|
||||
# Online install: use the configured archive as the primary apt mirror so
|
||||
# Subiquity/curtin can fetch whatever the minimal media lacks. No
|
||||
@@ -1792,6 +1877,7 @@ sub ubuntu_subiquity_apt_config
|
||||
' apt:',
|
||||
' preserve_sources_list: false',
|
||||
' geoip: false',
|
||||
q( conf: 'APT::Install-Recommends "false";'),
|
||||
' mirror-selection:',
|
||||
' primary:',
|
||||
" - uri: $online_mirror",
|
||||
@@ -1809,12 +1895,16 @@ sub ubuntu_subiquity_apt_config
|
||||
push @lines, ' xcat-ubuntu-updates.list:';
|
||||
push @lines, qq( source: "deb $online_mirror \$RELEASE-updates main restricted universe multiverse");
|
||||
}
|
||||
if (@otherpkg_sources) {
|
||||
if (@otherpkg_sources || @pkgdir_sources) {
|
||||
push @lines, ' sources:' unless $need_sources_block;
|
||||
my $index = 0;
|
||||
foreach my $source (@otherpkg_sources) {
|
||||
push @lines, " xcat-otherpkgs-$index.list:";
|
||||
push @lines, qq( source: "deb [trusted=yes] $source ./");
|
||||
push @lines, ubuntu_subiquity_source_lines( "xcat-otherpkgs-$index", $source, $use_deb822 );
|
||||
$index++;
|
||||
}
|
||||
$index = 0;
|
||||
foreach my $source (@pkgdir_sources) {
|
||||
push @lines, ubuntu_subiquity_source_lines( "xcat-pkgdir-$index", $source, $use_deb822 );
|
||||
$index++;
|
||||
}
|
||||
}
|
||||
@@ -1826,6 +1916,7 @@ sub ubuntu_subiquity_apt_config
|
||||
' preserve_sources_list: false',
|
||||
' fallback: offline-install',
|
||||
' geoip: false',
|
||||
q( conf: 'APT::Install-Recommends "false";'),
|
||||
' disable_suites:',
|
||||
' - updates',
|
||||
' - backports',
|
||||
@@ -1855,22 +1946,36 @@ sub ubuntu_subiquity_apt_config
|
||||
foreach my $source (@otherpkg_sources) {
|
||||
push @lines, '';
|
||||
push @lines, ' Types: deb';
|
||||
push @lines, " URIs: $source";
|
||||
push @lines, ' Suites: ./';
|
||||
push @lines, ' Components:';
|
||||
push @lines, " URIs: $source->{uri}";
|
||||
push @lines, " Suites: $source->{suites}";
|
||||
push @lines, ' Components:' . ( length $source->{components} ? " $source->{components}" : '' );
|
||||
push @lines, ' Trusted: yes';
|
||||
}
|
||||
foreach my $source (@pkgdir_sources) {
|
||||
push @lines, '';
|
||||
push @lines, ' Types: deb';
|
||||
push @lines, " URIs: $source->{uri}";
|
||||
push @lines, " Suites: $source->{suites}";
|
||||
push @lines, ' Components:' . ( length $source->{components} ? " $source->{components}" : '' );
|
||||
push @lines, ' Trusted: yes' if $source->{trusted};
|
||||
}
|
||||
} else {
|
||||
push @lines, ' mirror-selection:';
|
||||
push @lines, ' primary:';
|
||||
push @lines, ' - uri: file:/cdrom';
|
||||
|
||||
if (@otherpkg_sources) {
|
||||
if (@otherpkg_sources || @pkgdir_sources) {
|
||||
push @lines, ' sources:';
|
||||
my $index = 0;
|
||||
foreach my $source (@otherpkg_sources) {
|
||||
push @lines, " xcat-otherpkgs-$index.list:";
|
||||
push @lines, qq( source: "deb [trusted=yes] $source ./");
|
||||
push @lines, qq( source: "$source->{line}");
|
||||
$index++;
|
||||
}
|
||||
$index = 0;
|
||||
foreach my $source (@pkgdir_sources) {
|
||||
push @lines, " xcat-pkgdir-$index.list:";
|
||||
push @lines, qq( source: "$source->{line}");
|
||||
$index++;
|
||||
}
|
||||
}
|
||||
@@ -1879,6 +1984,56 @@ sub ubuntu_subiquity_apt_config
|
||||
return join( "\n", @lines );
|
||||
}
|
||||
|
||||
# ubuntu_subiquity_pkgdir_source_specs: the apt sources of the entries after the install media in
|
||||
# an osimage pkgdir value, which mkinstall hands over as pkgdirs and ospkgs receives as OSPKGDIR.
|
||||
# An entry written as "URL suite components" is an apt source line, as ospkgs writes it, and a
|
||||
# suite that is an exact path needs no component. A local directory that is a flat repository is
|
||||
# served by the management node and trusted, as an otherpkgdir is. Anything else is no apt source
|
||||
# for ospkgs either and is left out. An entry that names an Ubuntu archive mirror the installer
|
||||
# already has a source for, the configured one or a default one, carries that source's signing
|
||||
# key, the archive keyring on the Deb822 releases and none before them: apt rejects a second source
|
||||
# for the same suite whose signing key differs.
|
||||
sub ubuntu_subiquity_pkgdir_source_specs
|
||||
{
|
||||
my ( $pkgdirval, $mirror_key, @mirrors ) = @_;
|
||||
$mirror_key //= '';
|
||||
my %signed_uri = ubuntu_subiquity_signed_mirror_uris(@mirrors);
|
||||
my @specs;
|
||||
foreach my $entry ( split( /,/, $pkgdirval // '' ) ) {
|
||||
$entry =~ s/^\s+|\s+$//g;
|
||||
next if $entry eq '';
|
||||
if ( $entry =~ m{^https?://} ) {
|
||||
my ( $uri, $suite, @components ) = split( /\s+/, $entry );
|
||||
next unless defined $suite && ( @components || $suite =~ m{/$} );
|
||||
( my $bare = $uri ) =~ s{/+$}{};
|
||||
my %spec = ( uri => $uri, suites => $suite, components => join( ' ', @components ), trusted => 0, signed_by => $signed_uri{$bare} ? $mirror_key : '' );
|
||||
$spec{line} = ubuntu_subiquity_source_line( \%spec );
|
||||
push @specs, \%spec;
|
||||
}
|
||||
elsif ( $entry !~ m{^[a-z]+://} && ubuntu_subiquity_local_apt_repo($entry) ) {
|
||||
my $uri = ubuntu_subiquity_pkgdir_uri($entry);
|
||||
my %spec = ( uri => $uri, suites => './', components => '', trusted => 1, signed_by => '' );
|
||||
$spec{line} = ubuntu_subiquity_source_line( \%spec );
|
||||
push @specs, \%spec;
|
||||
}
|
||||
}
|
||||
|
||||
# a directory and its own URL are one repository: one source, with the trust and components of both
|
||||
my ( %kept, @unique );
|
||||
foreach my $spec (@specs) {
|
||||
( my $uri = $spec->{uri} ) =~ s{/+$}{};
|
||||
if ( my $first = $kept{"$uri $spec->{suites}"} ) {
|
||||
$first->{trusted} ||= $spec->{trusted};
|
||||
$first->{signed_by} ||= $spec->{signed_by};
|
||||
ubuntu_subiquity_add_components( $first, $spec->{components} );
|
||||
next;
|
||||
}
|
||||
push @unique, $kept{"$uri $spec->{suites}"} = $spec;
|
||||
}
|
||||
return @unique;
|
||||
}
|
||||
|
||||
|
||||
sub ubuntu_subiquity_otherpkg_sources
|
||||
{
|
||||
my $nodetype_tab = xCAT::Table->new('nodetype');
|
||||
@@ -1906,6 +2061,73 @@ sub ubuntu_subiquity_otherpkg_sources
|
||||
return @sources;
|
||||
}
|
||||
|
||||
# ubuntu_subiquity_add_components: the components of a repeated repository join the source kept for it.
|
||||
sub ubuntu_subiquity_add_components
|
||||
{
|
||||
my ( $spec, $components ) = @_;
|
||||
my %have = map { $_ => 1 } split( ' ', $spec->{components} );
|
||||
$spec->{components} = join( ' ', split( ' ', $spec->{components} ), grep { !$have{$_}++ } split( ' ', $components // '' ) );
|
||||
$spec->{line} = ubuntu_subiquity_source_line($spec);
|
||||
return;
|
||||
}
|
||||
|
||||
# ubuntu_subiquity_signed_mirror_uris: the apt mirror the installer already has a source for, the
|
||||
# configured one or the architecture default, without a trailing slash.
|
||||
sub ubuntu_subiquity_signed_mirror_uris
|
||||
{
|
||||
my (@mirrors) = @_;
|
||||
return map { ( my $uri = $_ ) =~ s{/+$}{}; ( $uri => 1 ) } grep { defined && length } @mirrors;
|
||||
}
|
||||
|
||||
# ubuntu_subiquity_source_line: the one-line form of a source, with the option its Deb822 form carries.
|
||||
sub ubuntu_subiquity_source_line
|
||||
{
|
||||
my ($spec) = @_;
|
||||
my @option = $spec->{trusted} ? ('[trusted=yes]') : $spec->{signed_by} ? ("[signed-by=$spec->{signed_by}]") : ();
|
||||
return join( ' ', 'deb', @option, $spec->{uri}, $spec->{suites}, grep { length } $spec->{components} );
|
||||
}
|
||||
|
||||
# ubuntu_subiquity_otherpkg_source_spec: the apt source of one otherpkgdir entry the installer gets.
|
||||
# A bare URL or a local repository is a flat trusted repository, as otherpkgs treats it. An entry
|
||||
# written as URL, suite and components is that source, trusted as well, unless the URL is an Ubuntu
|
||||
# archive mirror the installer already has a source for: that one gets the same signing key and no
|
||||
# trust, since apt rejects a second source for one suite whose options differ.
|
||||
sub ubuntu_subiquity_otherpkg_source_spec
|
||||
{
|
||||
my ( $entry, $mirror_key, @mirrors ) = @_;
|
||||
my ( $uri, $suite, @components ) = split( /\s+/, $entry );
|
||||
my %spec = ( uri => $uri, suites => './', components => '', trusted => 1, signed_by => '' );
|
||||
if ( defined $suite && length $suite ) {
|
||||
my %signed = ubuntu_subiquity_signed_mirror_uris(@mirrors);
|
||||
( my $bare = $uri ) =~ s{/+$}{};
|
||||
@spec{qw(suites components)} = ( $suite, join( ' ', @components ) );
|
||||
@spec{qw(trusted signed_by)} = ( 0, $mirror_key // '' ) if $signed{$bare};
|
||||
}
|
||||
$spec{line} = ubuntu_subiquity_source_line( \%spec );
|
||||
return \%spec;
|
||||
}
|
||||
|
||||
# ubuntu_subiquity_source_lines: one entry of the autoinstall sources mapping, a one-line source
|
||||
# before Deb822 and a Deb822 stanza from 24.04 on: curtin converts a one-line source to Deb822
|
||||
# there and keeps only its type, URI, suite and components, so a trusted repository would come out
|
||||
# unsigned and be rejected.
|
||||
sub ubuntu_subiquity_source_lines
|
||||
{
|
||||
my ( $name, $source, $use_deb822 ) = @_;
|
||||
return ( " $name.list:", qq( source: "$source->{line}") ) unless $use_deb822;
|
||||
my @lines = (
|
||||
" $name.sources:",
|
||||
' source: |',
|
||||
' Types: deb',
|
||||
" URIs: $source->{uri}",
|
||||
" Suites: $source->{suites}",
|
||||
' Components:' . ( length $source->{components} ? " $source->{components}" : '' ),
|
||||
);
|
||||
push @lines, " Signed-By: $source->{signed_by}" if $source->{signed_by};
|
||||
push @lines, ' Trusted: yes' if $source->{trusted};
|
||||
return @lines;
|
||||
}
|
||||
|
||||
sub ubuntu_subiquity_uses_deb822_sources
|
||||
{
|
||||
my ($media_dir) = @_;
|
||||
|
||||
@@ -192,6 +192,9 @@ my %INSTALL_BOOT_FILES = (
|
||||
[ 'install/netboot/ubuntu-installer/{darch}/vmlinux', 'install/netboot/ubuntu-installer/{darch}/initrd.gz' ],
|
||||
[ 'install/vmlinux', 'install/netboot/initrd.gz' ],
|
||||
],
|
||||
'riscv64' => [
|
||||
[ 'casper/vmlinux', 'casper/initrd' ],
|
||||
],
|
||||
);
|
||||
|
||||
sub install_boot_files
|
||||
@@ -201,9 +204,10 @@ sub install_boot_files
|
||||
$darch = '' unless defined $darch;
|
||||
|
||||
my $family =
|
||||
$arch =~ /x86/i ? 'x86'
|
||||
: $arch =~ /ppc64/i ? 'ppc64'
|
||||
: undef;
|
||||
$arch =~ /x86/i ? 'x86'
|
||||
: $arch =~ /ppc64/i ? 'ppc64'
|
||||
: $arch =~ /riscv64/i ? 'riscv64'
|
||||
: undef;
|
||||
return unless $family;
|
||||
|
||||
foreach my $candidate (@{ $INSTALL_BOOT_FILES{$family} }) {
|
||||
@@ -215,6 +219,166 @@ sub install_boot_files
|
||||
return;
|
||||
}
|
||||
|
||||
# The grub2 image on the media boots only from the media: it carries a built-in
|
||||
# configuration that looks for the live filesystem and never reads the network
|
||||
# configuration nodeset writes. A netboot image is built from the grub2 package
|
||||
# the media ships instead.
|
||||
my %MEDIA_GRUB2_BUILDS = (
|
||||
'riscv64' => {
|
||||
format => 'riscv64-efi',
|
||||
package => 'grub-efi-riscv64-bin',
|
||||
machine => 0x5064,
|
||||
},
|
||||
);
|
||||
|
||||
# Where the loader looks for the configuration nodeset writes, stored inside the image.
|
||||
my $GRUB2_PREFIX = '/boot/grub2';
|
||||
|
||||
# The modules the network path needs before it can read a configuration file.
|
||||
my @GRUB2_NETBOOT_MODULES = qw(
|
||||
efinet tftp http net normal linux echo test configfile
|
||||
search search_label search_fs_uuid search_fs_file
|
||||
gzio part_gpt part_msdos ext2 fat all_video video font terminal reboot halt
|
||||
);
|
||||
|
||||
sub install_media_grub2_loader {
|
||||
my ($path, $arch, $callback) = @_;
|
||||
|
||||
my $build = $MEDIA_GRUB2_BUILDS{$arch};
|
||||
return unless $build;
|
||||
|
||||
my $tftpdir = xCAT::TableUtils->getTftpDir();
|
||||
unless ($tftpdir) {
|
||||
_no_grub2_loader($arch, 'the TFTP directory is not known', $callback);
|
||||
return;
|
||||
}
|
||||
my $target = "$tftpdir/boot/grub2/grub2.$arch";
|
||||
return $target if _is_netboot_loader($target, $build);
|
||||
|
||||
# A file that failed the check is left where it is. The check cannot tell an image built for
|
||||
# another boot path from one this plugin did not build: the loader on the media carries the
|
||||
# same modules and differs only in the prefix. Moving a file aside on that evidence can take
|
||||
# a working loader away from every node of the architecture, so it is replaced only once a
|
||||
# working one exists, by the rename below.
|
||||
my $kept = -e $target ? 1 : 0;
|
||||
|
||||
my ($package) = glob("$path/pool/main/g/grub2/$build->{package}_*_$arch.deb");
|
||||
unless ($package && -r $package) {
|
||||
_no_grub2_loader($arch, "the media carry no $build->{package} package", $callback, $kept);
|
||||
return;
|
||||
}
|
||||
|
||||
my $workdir = tempdir(CLEANUP => 1);
|
||||
if (system('dpkg-deb', '-x', $package, $workdir) != 0) {
|
||||
_no_grub2_loader($arch, "$package could not be unpacked", $callback, $kept);
|
||||
return;
|
||||
}
|
||||
|
||||
my $moduledir = "$workdir/usr/lib/grub/$build->{format}";
|
||||
unless (-d $moduledir) {
|
||||
_no_grub2_loader($arch, "$package carries no $build->{format} modules", $callback, $kept);
|
||||
return;
|
||||
}
|
||||
|
||||
mkpath("$tftpdir/boot/grub2");
|
||||
|
||||
# Built beside the target and renamed, so an interrupted run cannot leave a partial
|
||||
# loader that nodeset would hand to every node of the architecture.
|
||||
my $partial = "$target.$$";
|
||||
my $rc = system('grub-mkimage', '-O', $build->{format}, '-d', $moduledir,
|
||||
'-p', $GRUB2_PREFIX, '-o', $partial, @GRUB2_NETBOOT_MODULES);
|
||||
unless ($rc == 0 and _is_netboot_loader($partial, $build)) {
|
||||
unlink $partial;
|
||||
_no_grub2_loader($arch, 'grub-mkimage could not build it', $callback, $kept);
|
||||
return;
|
||||
}
|
||||
chmod 0644, $partial;
|
||||
unless (rename($partial, $target)) {
|
||||
unlink $partial;
|
||||
_no_grub2_loader($arch, "it could not be renamed to $target: $!", $callback, $kept);
|
||||
return;
|
||||
}
|
||||
$callback->({ data => "Installed $target from the media" }) if $callback;
|
||||
return $target;
|
||||
}
|
||||
|
||||
# UEFI loads the loader as a PE image for one machine, so anything else -- a truncated
|
||||
# file, a stub carrying only headers, or the loader of another architecture -- cannot boot
|
||||
# a node and is replaced. The fields below are the ones an image must have to be executed
|
||||
# at all: sections to load, an entry point to jump to, and the subsystem UEFI runs.
|
||||
sub _is_uefi_image {
|
||||
my ($file, $machine) = @_;
|
||||
|
||||
my $size = -s $file;
|
||||
return 0 unless ($machine and $size);
|
||||
open(my $fh, '<', $file) or return 0;
|
||||
binmode($fh);
|
||||
|
||||
my $ok = 0;
|
||||
my ($dos, $coff, $optional);
|
||||
if (read($fh, $dos, 64) == 64
|
||||
and substr($dos, 0, 2) eq 'MZ'
|
||||
and seek($fh, unpack('V', substr($dos, 60, 4)), 0)
|
||||
and read($fh, $coff, 24) == 24
|
||||
and substr($coff, 0, 4) eq "PE\0\0"
|
||||
and unpack('v', substr($coff, 4, 2)) == $machine
|
||||
and unpack('v', substr($coff, 6, 2)) > 0
|
||||
and read($fh, $optional, 72) == 72
|
||||
and unpack('v', substr($optional, 0, 2)) == 0x20b)
|
||||
{
|
||||
my $entry = unpack('V', substr($optional, 16, 4));
|
||||
my $image = unpack('V', substr($optional, 56, 4));
|
||||
my $headers = unpack('V', substr($optional, 60, 4));
|
||||
my $system = unpack('v', substr($optional, 68, 2));
|
||||
|
||||
# 10 is the EFI application subsystem, the only one the firmware loads.
|
||||
$ok = ($entry and $image and $headers and $system == 10
|
||||
and $headers <= $size and $image <= $size);
|
||||
}
|
||||
close($fh);
|
||||
return $ok ? 1 : 0;
|
||||
}
|
||||
|
||||
# The modules a net boot cannot happen without. grub-mkimage records the name of every module
|
||||
# it embeds, so their absence says the file is not a grub2 image built for this boot path,
|
||||
# whatever its headers claim.
|
||||
my @GRUB2_REQUIRED_MODULES = qw(efinet tftp http linux normal configfile search);
|
||||
|
||||
# grub-mkimage stores the prefix inside the image, so a loader built for this boot path
|
||||
# carries the directory nodeset writes its configuration into. The image on the media has
|
||||
# the same modules but no such prefix, which is why it cannot find that configuration: a
|
||||
# file without it is replaced rather than trusted.
|
||||
sub _is_netboot_loader {
|
||||
my ($file, $build) = @_;
|
||||
|
||||
return 0 unless _is_uefi_image($file, $build->{machine});
|
||||
open(my $fh, '<', $file) or return 0;
|
||||
binmode($fh);
|
||||
my $image = do { local $/; <$fh> };
|
||||
close($fh);
|
||||
return 0 unless defined $image and index($image, $GRUB2_PREFIX) >= 0;
|
||||
for my $module (@GRUB2_REQUIRED_MODULES) {
|
||||
return 0 if index($image, "\0$module\0") < 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
# Nothing else on an Ubuntu management node installs this loader, so a node of this
|
||||
# architecture cannot boot until an administrator supplies one.
|
||||
sub _no_grub2_loader {
|
||||
my ($arch, $reason, $callback, $kept) = @_;
|
||||
|
||||
return unless $callback;
|
||||
my $consequence = $kept
|
||||
? "The grub2.$arch already in the boot/grub2 directory of the TFTP root was left alone. It "
|
||||
. "was not built for this boot path, so check that nodes of this architecture still boot."
|
||||
: "Nodes of this architecture will not boot until one is placed in the boot/grub2 "
|
||||
. "directory of the TFTP root.";
|
||||
$callback->({
|
||||
warning => [ "No grub2.$arch boot loader was installed because $reason. $consequence" ] });
|
||||
return;
|
||||
}
|
||||
|
||||
sub is_ubuntu_live_media
|
||||
{
|
||||
my $media_path = shift;
|
||||
@@ -517,6 +681,7 @@ sub copycd
|
||||
}
|
||||
|
||||
$callback->({ data => "Media copy operation successful" });
|
||||
install_media_grub2_loader($temppath, $arch, $callback);
|
||||
unless ($noosimage) {
|
||||
my @ret = xCAT::SvrUtils->update_tables_with_templates($distname, $arch, $temppath, $osdistroname, $legacyUB20);
|
||||
if ($ret[0] != 0) {
|
||||
@@ -645,6 +810,15 @@ sub subiquity_boot_params {
|
||||
return (subiquity_kcmdline($base, $nfsip, $pkgdir, $instserver, $httpport, $node), undef);
|
||||
}
|
||||
|
||||
# The Debian name of an install architecture, and whether xCAT installs Ubuntu
|
||||
# on it. ppc64le is kept as is: the media paths key on both spellings.
|
||||
my %INSTALL_ARCH = map { $_ => 1 } qw(x86_64 x86 ppc64le ppc64el riscv64);
|
||||
|
||||
sub install_darch {
|
||||
my ($arch) = @_;
|
||||
return ( xCAT::Utils::debian_arch($arch), $INSTALL_ARCH{ $arch // '' } ? 1 : 0 );
|
||||
}
|
||||
|
||||
sub mkinstall {
|
||||
xCAT::MsgUtils->message("S", "Doing debian mkinstall");
|
||||
my $request = shift;
|
||||
@@ -721,6 +895,7 @@ sub mkinstall {
|
||||
my $partitionfile;
|
||||
my $pkgdir;
|
||||
my $pkgdirval;
|
||||
my $environvar;
|
||||
my @mirrors;
|
||||
my $pkglistfile;
|
||||
my $imagename; # set it if running of 'nodeset osimage=xxx'
|
||||
@@ -743,12 +918,13 @@ sub mkinstall {
|
||||
if (!$osimagetab) {
|
||||
$osimagetab = xCAT::Table->new('osimage', -create => 1);
|
||||
}
|
||||
(my $ref) = $osimagetab->getAttribs({ imagename => $imagename }, 'osvers', 'osarch', 'profile', 'provmethod');
|
||||
(my $ref) = $osimagetab->getAttribs({ imagename => $imagename }, 'osvers', 'osarch', 'profile', 'provmethod', 'environvar');
|
||||
if ($ref) {
|
||||
$img_hash{$imagename}->{osver} = $ref->{'osvers'};
|
||||
$img_hash{$imagename}->{osarch} = $ref->{'osarch'};
|
||||
$img_hash{$imagename}->{profile} = $ref->{'profile'};
|
||||
$img_hash{$imagename}->{provmethod} = $ref->{'provmethod'};
|
||||
$img_hash{$imagename}->{environvar} = $ref->{'environvar'};
|
||||
if (!$linuximagetab) {
|
||||
$linuximagetab = xCAT::Table->new('linuximage', -create => 1);
|
||||
}
|
||||
@@ -821,6 +997,7 @@ sub mkinstall {
|
||||
|
||||
$tmplfile = $ph->{template};
|
||||
$pkgdirval = $ph->{pkgdir};
|
||||
$environvar = $ph->{environvar};
|
||||
my @pkgdirlist = split(/,/, $pkgdirval);
|
||||
foreach (@pkgdirlist) {
|
||||
if ($_ =~ /^http|ssh/) {
|
||||
@@ -868,18 +1045,9 @@ sub mkinstall {
|
||||
xCAT::MsgUtils->trace($verbose_on_off, "d", "debian->mkinstall: pkgdir=$pkgdir pkglistfile=$pkglistfile tmplfile=$tmplfile");
|
||||
}
|
||||
|
||||
if ($arch eq "x86_64") {
|
||||
$darch = "amd64";
|
||||
}
|
||||
elsif ($arch eq "x86") {
|
||||
$darch = "i386";
|
||||
}
|
||||
else {
|
||||
if ($arch ne "ppc64le" and $arch ne "ppc64el") {
|
||||
xCAT::MsgUtils->message("S", "debian.pm: Unknown arch ($arch)");
|
||||
}
|
||||
$darch = $arch;
|
||||
}
|
||||
my $known;
|
||||
($darch, $known) = install_darch($arch);
|
||||
xCAT::MsgUtils->message("S", "debian.pm: Unknown arch ($arch)") unless $known;
|
||||
|
||||
my @missingparms;
|
||||
unless ($os) {
|
||||
@@ -957,7 +1125,10 @@ sub mkinstall {
|
||||
$pkgdir,
|
||||
$platform,
|
||||
$partitionfile,
|
||||
\%tmpl_hash
|
||||
\%tmpl_hash,
|
||||
osarch => $arch,
|
||||
pkgdirs => $pkgdirval,
|
||||
environvar => $environvar
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -85,6 +85,29 @@ sub getstate {
|
||||
}
|
||||
}
|
||||
|
||||
# grub2 reads its configuration as a script, so an unquoted word carrying one of the
|
||||
# characters below ends the linux command and the rest of the kernel command line is lost.
|
||||
# The Ubuntu installer seed (ds=nocloud-net;s=<url>) is the usual casualty.
|
||||
my $GRUB2_TERMINATOR = qr/[;{}|&<>()]/;
|
||||
|
||||
sub quote_kcmdline {
|
||||
my $kcmdline = shift;
|
||||
|
||||
return $kcmdline unless (defined $kcmdline and $kcmdline =~ $GRUB2_TERMINATOR);
|
||||
|
||||
# Escaped in place rather than quoted as a whole: a value the caller quoted keeps the
|
||||
# quoting it was given, which grub2 removes before the kernel sees the value.
|
||||
my $escaped = '';
|
||||
while (length $kcmdline) {
|
||||
if ($kcmdline =~ s/^('[^']*'|"[^"]*")//) { $escaped .= $1; next; }
|
||||
if ($kcmdline =~ s/^(\\.)//) { $escaped .= $1; next; }
|
||||
$kcmdline =~ s/^(.)//s;
|
||||
my $char = $1;
|
||||
$escaped .= ($char =~ $GRUB2_TERMINATOR) ? "\\$char" : $char;
|
||||
}
|
||||
return $escaped;
|
||||
}
|
||||
|
||||
sub setstate {
|
||||
|
||||
=pod
|
||||
@@ -260,7 +283,8 @@ sub setstate {
|
||||
}
|
||||
|
||||
if ($kern and $kern->{kcmdline}) {
|
||||
print $pcfg " linux$efi $protocolrootdir/$kern->{kernel} $kern->{kcmdline} BOOTIF=\$net_default_mac\n";
|
||||
my $kcmdline = quote_kcmdline($kern->{kcmdline});
|
||||
print $pcfg " linux$efi $protocolrootdir/$kern->{kernel} $kcmdline BOOTIF=\$net_default_mac\n";
|
||||
} else {
|
||||
print $pcfg " linux$efi $protocolrootdir/$kern->{kernel} BOOTIF=\$net_default_mac\n";
|
||||
}
|
||||
|
||||
@@ -907,9 +907,9 @@ sub process_request {
|
||||
}
|
||||
}
|
||||
if (exists $GRUB2_DISCOVERY_ARCHES{$arch} && !-e "$tftpdir/boot/grub2/grub2.$arch") {
|
||||
# These configurations are only reachable through grub2.<arch>, which xCAT
|
||||
# does not build.
|
||||
$callback->({ data => ["Note: $tftpdir/boot/grub2/grub2.$arch is missing; $arch nodes need it to reach these configurations (it is installed by grub2-xcat, or copied from the EL $arch installation media)"] });
|
||||
# These configurations are only reachable through grub2.<arch>. copycd builds it from
|
||||
# Ubuntu media; on EL it is supplied by grub2-xcat or copied from the media.
|
||||
$callback->({ data => ["Note: $tftpdir/boot/grub2/grub2.$arch is missing; $arch nodes need it to reach these configurations (copycd builds it from Ubuntu media; on EL it is installed by grub2-xcat or copied from the $arch installation media)"] });
|
||||
}
|
||||
if ($configfileonly && !$s390x_config_error) {
|
||||
$callback->({ data => ["Write netboot config file done"] });
|
||||
|
||||
@@ -100,10 +100,7 @@ if [ ! -x /usr/bin/wget ]; then
|
||||
sleep 36500d
|
||||
fi
|
||||
|
||||
# These dispatcher scripts are not needed by the legacy post.xcat path. Newer
|
||||
# wget parses HTML-looking regex strings inside downloaded scripts and fails the
|
||||
# whole recursive download on bogus URLs.
|
||||
wget -l inf -N -r --waitretry=10 --random-wait --retry-connrefused -e robots=off -nH --cut-dirs=2 --reject "index.html*,post.xcat.ng,post.xcat.rhels10" --no-parent -t 20 -T 60 http://${MASTER_IP}:${HTTPPORT}${INSTALLDIR}/postscripts/ -P /xcatpost 2> /tmp/wget.log
|
||||
xcat_download_postscripts "${MASTER_IP}:${HTTPPORT}" "$INSTALLDIR" "/xcatpost" "/tmp/wget.log"
|
||||
if [ "$?" != "0" ]; then
|
||||
msgutil_r "$MASTER_IP" "error" "failed to download postscripts from http://$MASTER_IP$INSTALLDIR/postscripts/,check /tmp/wget.log on the node, halt ..." "/var/log/xcat/xcat.log" "$log_label"
|
||||
/tmp/updateflag $MASTER $XCATIPORT "installstatus failed"
|
||||
|
||||
@@ -191,17 +191,6 @@ if [ -e "/tmp/xcat.install_disk" ]; then
|
||||
fi
|
||||
|
||||
msgutil_r "$MASTER_IP" "info" "Found $instdisk, generate partition file..." "/var/log/xcat/xcat.log" "$log_label"
|
||||
set_sles11_uefi_bootloader()
|
||||
{
|
||||
if grep -E 'install=.*sles11' /proc/cmdline >/dev/null 2>&1; then
|
||||
# SLES 11 AutoYaST keeps the template's legacy MBR bootloader
|
||||
# location unless the UEFI path explicitly selects elilo.
|
||||
sed -i -e '/<lba_support /d' \
|
||||
-e '/<linear /d' \
|
||||
-e 's!<location>mbr</location>!<loader_type>elilo</loader_type>!' \
|
||||
/tmp/profile/modified.xml
|
||||
fi
|
||||
}
|
||||
|
||||
if [ -d /sys/firmware/efi ]; then
|
||||
sed -e 's!<device>XCATPARTITIONHOOK</device>!<device>'$instdisk'</device><partitions config:type="list"><partition><filesystem config:type="symbol">vfat</filesystem><mount>/boot/efi</mount><size>128mb</size></partition><partition><mount>swap</mount><size>auto</size></partition><partition><mount>/</mount><size>auto</size></partition></partitions>!' /tmp/profile/autoinst.xml > /tmp/profile/modified.xml
|
||||
|
||||
@@ -63,3 +63,27 @@ declare -F xcat_enable_active_nm_autoconnect &>/dev/null || function xcat_enable
|
||||
nmcli con mod "$con_name" connection.autoconnect yes
|
||||
done
|
||||
}
|
||||
|
||||
declare -F xcat_download_postscripts &>/dev/null || function xcat_download_postscripts {
|
||||
local server="$1"
|
||||
local install_dir="${2:-/install}"
|
||||
local postroot="${3:-/xcatpost}"
|
||||
local log_file="${4:-/tmp/wget.log}"
|
||||
|
||||
export LANG=C
|
||||
wget -l inf -N -r --waitretry=10 --random-wait --retry-connrefused -e robots=off -nH --cut-dirs=2 --reject "index.html*,post.xcat.ng,post.xcat.rhels10" --no-parent -t 20 -T 60 "http://${server}${install_dir}/postscripts/" -P "$postroot" 2> "$log_file"
|
||||
}
|
||||
|
||||
declare -F set_sles11_uefi_bootloader &>/dev/null || function set_sles11_uefi_bootloader {
|
||||
local cmdline="${1:-/proc/cmdline}"
|
||||
local profile="${2:-/tmp/profile/modified.xml}"
|
||||
|
||||
if grep -E 'install=.*sles11' "$cmdline" >/dev/null 2>&1; then
|
||||
# SLES 11 AutoYaST keeps the template's legacy MBR bootloader
|
||||
# location unless the UEFI path explicitly selects elilo.
|
||||
sed -i -e '/<lba_support /d' \
|
||||
-e '/<linear /d' \
|
||||
-e 's!<location>mbr</location>!<loader_type>elilo</loader_type>!' \
|
||||
"$profile"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ autoinstall:
|
||||
- bind9-dnsutils
|
||||
- chrony
|
||||
- gpg
|
||||
- #INCLUDE_DEFAULT_PKGLIST_AUTOINSTALL#
|
||||
early-commands:
|
||||
- |
|
||||
exec >/tmp/pre-install.log 2>&1
|
||||
@@ -111,6 +112,11 @@ autoinstall:
|
||||
cp ./#HOSTNAME#.post /target/root/post.script;
|
||||
curtin in-target --target /target /root/post.script;
|
||||
} >>/target/var/log/xcat/xcat.log 2>&1'
|
||||
# The installer's sources for the otherpkgs repository and the pkgdir mirrors, and the apt
|
||||
# configuration that kept recommended packages out, served the install; ospkgs and otherpkgs
|
||||
# write their own after the first boot. A separate item, so the status of the post script above
|
||||
# still decides whether the install goes on.
|
||||
- rm -f /target/etc/apt/sources.list.d/xcat-otherpkgs-*.list /target/etc/apt/sources.list.d/xcat-otherpkgs-*.sources /target/etc/apt/sources.list.d/xcat-pkgdir-*.list /target/etc/apt/sources.list.d/xcat-pkgdir-*.sources /target/etc/apt/apt.conf.d/94curtin-config
|
||||
# Flip the node to local-disk boot, or it PXE-loops back into the installer on reboot.
|
||||
# xcatd's install monitor greets with "ready", then answers "next" with "done" and runs
|
||||
# "nodeset <node> next". Require both tokens: another service on that port is not a flipped
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
openssh-server
|
||||
chrony
|
||||
gawk
|
||||
nfs-common
|
||||
snmpd
|
||||
@@ -0,0 +1,5 @@
|
||||
openssh-server
|
||||
chrony
|
||||
gawk
|
||||
nfs-common
|
||||
snmpd
|
||||
@@ -0,0 +1,5 @@
|
||||
openssh-server
|
||||
chrony
|
||||
gawk
|
||||
nfs-common
|
||||
snmpd
|
||||
@@ -0,0 +1,15 @@
|
||||
bash
|
||||
nfs-common
|
||||
openssl
|
||||
isc-dhcp-client
|
||||
libc-bin
|
||||
openssh-server
|
||||
openssh-client
|
||||
wget
|
||||
vim
|
||||
rsync
|
||||
busybox-static
|
||||
gawk
|
||||
bind9-dnsutils
|
||||
chrony
|
||||
gpg
|
||||
@@ -278,6 +278,7 @@ sub default_net_drivers {
|
||||
x86_64 => [qw(tg3 bnx2 bnx2x e1000 e1000e igb mlx_en mlx5_core virtio_net overlay)],
|
||||
ppc64el => [qw(tg3 bnx2 bnx2x e1000 e1000e igb ibmveth ehea mlx_en mlx4_en mlx5_core virtio_net overlay)],
|
||||
ppc64 => [qw(e1000 e1000e igb ibmveth ehea)],
|
||||
riscv64 => [qw(e1000 e1000e igb ixgbe r8169 tg3 bnx2x mlx5_core virtio_net overlay)],
|
||||
s390x => [qw(qdio ccwgroup)],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
bash
|
||||
nfs-common
|
||||
openssl
|
||||
isc-dhcp-client
|
||||
libc-bin
|
||||
linux-image-generic
|
||||
openssh-server
|
||||
openssh-client
|
||||
wget
|
||||
rsync
|
||||
busybox-static
|
||||
gawk
|
||||
bind9-dnsutils
|
||||
tar
|
||||
gzip
|
||||
xz-utils
|
||||
cpio
|
||||
chrony
|
||||
util-linux-extra
|
||||
iproute2
|
||||
dracut
|
||||
dracut-network
|
||||
@@ -0,0 +1,18 @@
|
||||
bash
|
||||
nfs-common
|
||||
openssl
|
||||
isc-dhcp-client
|
||||
libc-bin
|
||||
linux-image-generic
|
||||
openssh-server
|
||||
openssh-client
|
||||
wget
|
||||
rsync
|
||||
busybox-static
|
||||
gawk
|
||||
bind9-dnsutils
|
||||
tar
|
||||
gzip
|
||||
xz-utils
|
||||
cpio
|
||||
chrony
|
||||
@@ -259,8 +259,13 @@ unless ($onlyinitrd) {
|
||||
# site.ubuntu_apt_mirror overrides; otherwise default to the public archive. A live-server
|
||||
# ISO is never a complete debootstrap source, so a real mirror is always required here.
|
||||
my @aptmirror = xCAT::TableUtils->get_site_attribute("ubuntu_apt_mirror");
|
||||
# archive.ubuntu.com publishes amd64 and i386 only. Every other architecture, ppc64el
|
||||
# and riscv64 included, is on the ports archive.
|
||||
my $default = ($uarch =~ /^(?:amd64|i386)$/)
|
||||
? 'http://archive.ubuntu.com/ubuntu'
|
||||
: 'http://ports.ubuntu.com/ubuntu-ports';
|
||||
my $mirror = (defined $aptmirror[0] && length $aptmirror[0])
|
||||
? $aptmirror[0] : 'http://archive.ubuntu.com/ubuntu';
|
||||
? $aptmirror[0] : $default;
|
||||
(my $codename = $osver) =~ s/^ubuntu//;
|
||||
# Strip ONLY a trailing third component. A bare s/\.\d+$// also strips the minor from a
|
||||
# two-part osvers (ubuntu26.04 -> "26"), which misses the %cn map below and reaches
|
||||
@@ -1786,6 +1791,11 @@ EOMS
|
||||
push @filestoadd, $_ if (-e "$rootimg_dir/$_");
|
||||
}
|
||||
|
||||
} elsif ($arch =~ /riscv64/) {
|
||||
foreach ("lib/riscv64-linux-gnu/libnss_files.so.2", "lib/riscv64-linux-gnu/libnss_dns.so.2") {
|
||||
push @filestoadd, $_ if (-e "$rootimg_dir/$_");
|
||||
}
|
||||
|
||||
} else {
|
||||
push @filestoadd, "lib/libnss_dns.so.2" if (-e "$rootimg_dir/lib/libnss_dns.so.2");
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ BEGIN {
|
||||
use lib "$::XCATROOT/lib/perl";
|
||||
use xCAT::CommandUtils;
|
||||
use IO::Socket::INET;
|
||||
use File::Temp qw(tempfile);
|
||||
use POSIX qw(_exit sigprocmask WNOHANG SIG_BLOCK SIG_SETMASK SIGINT SIGTERM);
|
||||
use Time::HiRes qw(gettimeofday sleep);
|
||||
use Getopt::Long;
|
||||
Getopt::Long::Configure("bundling");
|
||||
@@ -92,16 +94,25 @@ if (-f "/etc/redhat-release") {
|
||||
}
|
||||
|
||||
# fork a process to capture the packet by tcpdump
|
||||
my ($dumpfh, $dumpfile) = tempfile("detect_dhcpd.XXXXXX", TMPDIR => 1, UNLINK => 1);
|
||||
close($dumpfh);
|
||||
# INT and TERM stay blocked from the fork until the handler that stops the capture is in place.
|
||||
my $stop_signals = POSIX::SigSet->new(SIGINT, SIGTERM);
|
||||
my $signal_mask = POSIX::SigSet->new();
|
||||
sigprocmask(SIG_BLOCK, $stop_signals, $signal_mask);
|
||||
my $pid = fork;
|
||||
if (!defined $pid) { print "Fork failed.\n"; exit 1; }
|
||||
my $dumpfile = "/tmp/dhcpdumpfile.log";
|
||||
if (!defined $pid) { sigprocmask(SIG_SETMASK, $signal_mask); print "Fork failed.\n"; exit 1; }
|
||||
if ($pid == 0) {
|
||||
|
||||
# Child process
|
||||
my $cmd = "$tcpdump -i $IF port 68 -n -vvvvvv > $dumpfile 2>/dev/null";
|
||||
`$cmd`;
|
||||
exit 0;
|
||||
# Child process: tcpdump itself, so the parent owns exactly this pid.
|
||||
sigprocmask(SIG_SETMASK, $signal_mask);
|
||||
open(STDOUT, '>', $dumpfile) or _exit(1);
|
||||
open(STDERR, '>', '/dev/null');
|
||||
exec($tcpdump, '-i', $nic, 'port', '68', '-n', '-vvvvvv');
|
||||
_exit(1);
|
||||
}
|
||||
$SIG{INT} = $SIG{TERM} = sub { kill_child(); exit 1; };
|
||||
sigprocmask(SIG_SETMASK, $signal_mask);
|
||||
|
||||
# generate the discover package
|
||||
my $package = packdhcppkg($MAC);
|
||||
@@ -141,25 +152,24 @@ if ($::TIMEOUT) {
|
||||
my $end = Time::HiRes::gettimeofday();
|
||||
$end =~ s/(\d.*)\.(\d.*)/$1/;
|
||||
while ($end - $start <= $timeout) {
|
||||
$sock->send($package) or die "Send discover error: $@\n";
|
||||
unless ($sock->send($package)) {
|
||||
print "Send discover error: $!\n";
|
||||
kill_child();
|
||||
exit 1;
|
||||
}
|
||||
sleep 2;
|
||||
$end = Time::HiRes::gettimeofday();
|
||||
$end =~ s/(\d.*)\.(\d.*)/$1/;
|
||||
}
|
||||
|
||||
|
||||
kill_child();
|
||||
|
||||
#kill the child process
|
||||
kill 15, $pid;
|
||||
my @pidoftcpdump = `ps -ef | grep -E "[0-9]+:[0-9]+:[0-9]+ $tcpdump -i $IF" | awk -F' ' '{print \$2}'`;
|
||||
foreach my $cpid (@pidoftcpdump) {
|
||||
kill 15, $cpid;
|
||||
|
||||
# print "try to kill $cpid\n";
|
||||
my $capture_problem = kill_child();
|
||||
if ($capture_problem) {
|
||||
print "tcpdump $capture_problem.\n";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
sleep 2;
|
||||
open(FILE, "<$dumpfile") or die "Cannot open $dumpfile\n";
|
||||
my %output;
|
||||
my @snack = ();
|
||||
@@ -239,7 +249,6 @@ if (scalar(@server)) {
|
||||
}
|
||||
}
|
||||
|
||||
#`rm -f $dumpfile`;
|
||||
exit 0;
|
||||
|
||||
|
||||
@@ -345,12 +354,38 @@ sub packdhcppkg {
|
||||
return $package;
|
||||
}
|
||||
|
||||
# Stop the capture and reap it, once: INT and TERM are blocked while the pid is taken and until
|
||||
# tcpdump is reaped, so an interrupt in that window cannot orphan it or reach a recycled pid. The
|
||||
# handlers stay installed, so a later signal still leaves through exit and the END cleanup.
|
||||
# Returns '' when tcpdump ran until this TERM and stopped cleanly, otherwise what went wrong: it
|
||||
# ended on its own, ignored TERM and was killed, or left on another signal or with a non-zero
|
||||
# status.
|
||||
sub kill_child {
|
||||
kill 15, $pid;
|
||||
my @pidoftcpdump = `ps -ef | grep -E "[0-9]+:[0-9]+:[0-9]+ $tcpdump -i $IF" | awk -F' ' '{print \$2}'`;
|
||||
foreach my $cpid (@pidoftcpdump) {
|
||||
kill 15, $cpid;
|
||||
|
||||
#print "try to kill $cpid\n";
|
||||
sigprocmask(SIG_BLOCK, $stop_signals);
|
||||
my $child = $pid;
|
||||
$pid = undef;
|
||||
unless ($child) {
|
||||
sigprocmask(SIG_SETMASK, $signal_mask);
|
||||
return '';
|
||||
}
|
||||
my $reaped = waitpid($child, WNOHANG);
|
||||
my $early = ($reaped == $child) ? 1 : 0;
|
||||
if ($reaped == 0) {
|
||||
kill 'TERM', $child;
|
||||
foreach (1 .. 50) {
|
||||
last if ($reaped = waitpid($child, WNOHANG)) != 0;
|
||||
select(undef, undef, undef, 0.1);
|
||||
}
|
||||
if ($reaped == 0) {
|
||||
kill 'KILL', $child;
|
||||
$reaped = waitpid($child, 0);
|
||||
}
|
||||
}
|
||||
sigprocmask(SIG_SETMASK, $signal_mask);
|
||||
return "could not be reaped" if $reaped != $child;
|
||||
my ($signal, $status) = ($? & 127, $? >> 8);
|
||||
my $how = $signal ? "on signal $signal" : "with status $status";
|
||||
return "ended before the capture window did, $how" if $early;
|
||||
return '' if $signal == 15 || (!$signal && !$status);
|
||||
return "left the capture $how";
|
||||
}
|
||||
|
||||
@@ -2461,13 +2461,45 @@ case "${GO_XCAT_OS}" in
|
||||
esac
|
||||
|
||||
case "${GO_XCAT_ARCH}" in
|
||||
"ppc64"|"ppc64le"|"x86_64")
|
||||
"ppc64"|"ppc64le"|"riscv64"|"x86_64")
|
||||
;;
|
||||
*)
|
||||
exit_if_bad 1 "${GO_XCAT_ARCH}: unsupported instruction set architecture"
|
||||
;;
|
||||
esac
|
||||
|
||||
# A riscv64 management node has no legacy Genesis: its image ships as the OpenEmbedded package,
|
||||
# which mknb installs. The legacy Genesis scripts and bases are therefore dropped and the
|
||||
# OpenEmbedded package of the architecture is asked for instead.
|
||||
#
|
||||
# The x86 boot loaders stay: they are payload a management node SERVES to x86 nodes over TFTP,
|
||||
# not host binaries, so a riscv64 management node needs them to boot a mixed cluster.
|
||||
function riscv64_install_list()
|
||||
{
|
||||
local genesis_package="xCAT-genesis-openembedded-riscv64"
|
||||
type dpkg >/dev/null 2>&1 && genesis_package="xcat-genesis-openembedded-riscv64"
|
||||
local package
|
||||
for package in "$@"; do
|
||||
case "${package}" in
|
||||
*genesis-scripts-*|*genesis-base-*)
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' "${package}"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
printf '%s\n' "${genesis_package}"
|
||||
}
|
||||
|
||||
if [ "${GO_XCAT_ARCH}" = "riscv64" ]; then
|
||||
riscv64_list=()
|
||||
while read -r package; do
|
||||
riscv64_list+=("${package}")
|
||||
done < <(riscv64_install_list "${GO_XCAT_INSTALL_LIST[@]}")
|
||||
GO_XCAT_INSTALL_LIST=("${riscv64_list[@]}")
|
||||
unset riscv64_list package
|
||||
fi
|
||||
|
||||
GO_XCAT_LINUX_DISTRO="$(check_linux_distro)"
|
||||
GO_XCAT_LINUX_VERSION="$(check_linux_version)"
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# xCAT-test
|
||||
|
||||
Unit tests that run from the source checkout are split by implementation
|
||||
language:
|
||||
|
||||
| Test type | Location | Runner |
|
||||
| --------- | -------- | ------ |
|
||||
| Perl unit tests | `xCAT-test/unit/*.t` | `prove -r xCAT-test/unit` |
|
||||
| Shell unit tests | `xCAT-test/bats/*.bats` | `bats -r xCAT-test/bats` |
|
||||
| CLI functional tests | `xCAT-test/autotest/testcase/` and `xCAT-test/autotest/bundle/` | `xcattest -f <cluster.conf> -t <case>` or `xcattest -f <cluster.conf> -b <bundle>` |
|
||||
|
||||
Use Perl `.t` tests for Perl modules, Perl scripts, templates, and repository
|
||||
artifacts. Use BATS tests for shell-script behavior that can be exercised from
|
||||
the checkout by sourcing a shell library or script and shadowing external
|
||||
commands.
|
||||
|
||||
Shell behavior should not be tested by Perl tests that grep shell source. Put
|
||||
those tests under `xCAT-test/bats` instead.
|
||||
|
||||
See `unit/README.md` and `bats/README.md` for the detailed rules for
|
||||
each unit-test suite.
|
||||
@@ -0,0 +1,21 @@
|
||||
# xCAT-test/bats
|
||||
|
||||
Shell-script unit tests live here and run with:
|
||||
|
||||
```bash
|
||||
bats -r xCAT-test/bats
|
||||
```
|
||||
|
||||
The GitHub Actions `xcat_test` workflow runs this command after the Perl `.t`
|
||||
unit tests. Use BATS for shell behavior that can be exercised from the source
|
||||
tree without an installed xCAT, a live management node, or real services.
|
||||
|
||||
Prefer sourcing an existing shell library or sourceable script and calling the
|
||||
function under test. Keep reusable install-template helpers in
|
||||
`xCAT-server/share/xcat/install/scripts/scriptlib`, and reusable postscript
|
||||
helpers in `xCAT/postscripts/xcatlib.sh`. Use scratch directories and shadowed
|
||||
commands so tests cannot write to the host.
|
||||
|
||||
Extraction helpers in `helpers/shell_source.bash` are only for legacy code that
|
||||
cannot safely be sourced yet. Do not add Perl `.t` tests that grep shell source
|
||||
when the behavior can be tested with BATS.
|
||||
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
setup()
|
||||
{
|
||||
GENESIS_SPEC="$(repo_path 'xCAT-genesis-builder/xCAT-genesis-base.spec')"
|
||||
DRACUT_MODULE="$(repo_path 'xCAT-genesis-builder/dracut_105/el/module-setup.sh')"
|
||||
DOXCAT="$(repo_path 'xCAT-genesis-scripts/usr/bin/doxcat')"
|
||||
[ -r "$GENESIS_SPEC" ] || skip "$GENESIS_SPEC is required"
|
||||
[ -r "$DRACUT_MODULE" ] || skip "$DRACUT_MODULE is required"
|
||||
[ -r "$DOXCAT" ] || skip "$DOXCAT is required"
|
||||
export GENESIS_SPEC DRACUT_MODULE DOXCAT
|
||||
}
|
||||
|
||||
run_installkernel()
|
||||
{
|
||||
local modules_root="$1"
|
||||
local instmods_log="$2"
|
||||
|
||||
kernel=5.14.0-test
|
||||
DRACUT_MODULES_ROOT="$modules_root"
|
||||
instmods()
|
||||
{
|
||||
printf '%s\n' "$1" >>"$instmods_log"
|
||||
}
|
||||
|
||||
source "$DRACUT_MODULE"
|
||||
installkernel
|
||||
}
|
||||
|
||||
run_doxcat_modprobe_preamble()
|
||||
{
|
||||
local preamble="$1"
|
||||
local modprobe_log="$2"
|
||||
|
||||
modprobe()
|
||||
{
|
||||
printf '%s\n' "$*" >>"$modprobe_log"
|
||||
}
|
||||
|
||||
eval "$preamble"
|
||||
}
|
||||
|
||||
run_doxcat_bootif_block()
|
||||
{
|
||||
local block="$1"
|
||||
|
||||
BOOTIF=01-aa-bb-cc-dd-ee-ff
|
||||
bootnic=
|
||||
log_label=test
|
||||
gripeiter=2
|
||||
|
||||
logger() { :; }
|
||||
sleep() { :; }
|
||||
ip()
|
||||
{
|
||||
printf '%s\n' "$*" >>"$IP_LOG"
|
||||
if [ "$*" = "link show" ]; then
|
||||
cat <<'EOF'
|
||||
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
|
||||
link/ether 00:11:22:33:44:55 brd ff:ff:ff:ff:ff:ff
|
||||
3: ib0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65520 qdisc mq state UP mode DEFAULT group default qlen 256
|
||||
link/infiniband 00:bb:cc:dd:ee:ff brd 00:ff:ff:ff:ff:ff
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
|
||||
eval "$block"
|
||||
printf '%s\n' "$bootnic"
|
||||
}
|
||||
|
||||
@test "genesis build requires kernel module packages" {
|
||||
grep -Fxq 'BuildRequires: kernel-core' "$GENESIS_SPEC"
|
||||
grep -Fxq 'BuildRequires: kernel-modules' "$GENESIS_SPEC"
|
||||
grep -Fxq 'BuildRequires: kernel-modules-extra' "$GENESIS_SPEC"
|
||||
}
|
||||
|
||||
@test "dracut genesis module installs every module from modules.dep" {
|
||||
local modules_root="${BATS_TEST_TMPDIR}/modules"
|
||||
local modules_dep="${modules_root}/5.14.0-test/modules.dep"
|
||||
local instmods_log="${BATS_TEST_TMPDIR}/instmods.log"
|
||||
|
||||
mkdir -p "${modules_root}/5.14.0-test"
|
||||
cat >"$modules_dep" <<'EOF'
|
||||
kernel/drivers/infiniband/ulp/ipoib/ib_ipoib.ko.xz:
|
||||
kernel/drivers/net/ethernet/intel/e1000e/e1000e.ko.xz:
|
||||
EOF
|
||||
|
||||
run run_installkernel "$modules_root" "$instmods_log"
|
||||
[ "$status" -eq 0 ]
|
||||
grep -Fxq 'ib_ipoib' "$instmods_log"
|
||||
grep -Fxq 'e1000e' "$instmods_log"
|
||||
}
|
||||
|
||||
@test "doxcat loads IP over InfiniBand support during startup" {
|
||||
local preamble
|
||||
local modprobe_log="${BATS_TEST_TMPDIR}/modprobe.log"
|
||||
|
||||
preamble="$(extract_line_range "$DOXCAT" '^modprobe acpi_cpufreq' '^modprobe ib_ipoib$')" || return 1
|
||||
|
||||
run run_doxcat_modprobe_preamble "$preamble" "$modprobe_log"
|
||||
[ "$status" -eq 0 ]
|
||||
grep -Fxq 'ib_ipoib' "$modprobe_log"
|
||||
}
|
||||
|
||||
@test "doxcat falls back to InfiniBand BOOTIF lookup after Ethernet lookup misses" {
|
||||
local block
|
||||
|
||||
IP_LOG="${BATS_TEST_TMPDIR}/ip.log"
|
||||
export IP_LOG
|
||||
block="$(extract_shell_if_block "$DOXCAT" 'if [ ! -z "$BOOTIF" ]; then')" || return 1
|
||||
|
||||
run run_doxcat_bootif_block "$block"
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "ib0" ]
|
||||
[ "$(grep -c '^link show$' "$IP_LOG")" -eq 2 ]
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load 'helpers/go_xcat'
|
||||
|
||||
setup()
|
||||
{
|
||||
go_xcat_require_source
|
||||
}
|
||||
|
||||
run_common_repository_case()
|
||||
{
|
||||
local case_dir="$1"
|
||||
local common_present="$2"
|
||||
shift 2
|
||||
|
||||
mkdir -p "$case_dir"
|
||||
export ADD_LOG="${case_dir}/add.log"
|
||||
export COMMON_PRESENT="$common_present"
|
||||
export DOWNLOAD_LOG="${case_dir}/download.log"
|
||||
export ID_LOG="${case_dir}/id.log"
|
||||
export TEST_TMP="$case_dir"
|
||||
|
||||
go_xcat_load_functions \
|
||||
add_xcat_dep_common_repo_yum_or_zypper \
|
||||
xcat_dep_common_repo_configured \
|
||||
refresh_xcat_dep_repository_ids
|
||||
|
||||
TMP_DIR="$TEST_TMP"
|
||||
GO_XCAT_DEFAULT_BASE_URL=https://repo.example.invalid
|
||||
GO_XCAT_DEP_REPOSITORY_IDS=(xcat-dep)
|
||||
|
||||
yum() { :; }
|
||||
|
||||
download_file()
|
||||
{
|
||||
printf '%s\n' "$1" >>"$DOWNLOAD_LOG"
|
||||
[[ ${COMMON_PRESENT:-0} == 1 ]] || return 1
|
||||
: >"$2"
|
||||
}
|
||||
|
||||
add_repo_by_url_yum_or_zypper()
|
||||
{
|
||||
printf '%s %s\n' "$1" "$2" >>"$ADD_LOG"
|
||||
}
|
||||
|
||||
xcat_dep_common_repo_configured()
|
||||
{
|
||||
[[ -s "$ADD_LOG" ]]
|
||||
}
|
||||
|
||||
( add_xcat_dep_common_repo_yum_or_zypper "$@" )
|
||||
refresh_xcat_dep_repository_ids
|
||||
printf '%s\n' "${GO_XCAT_DEP_REPOSITORY_IDS[*]}" >"$ID_LOG"
|
||||
}
|
||||
|
||||
run_template_generation()
|
||||
{
|
||||
local tmp_dir="$1"
|
||||
local repo_log="$2"
|
||||
|
||||
mkdir -p "$tmp_dir"
|
||||
export REPO_LOG="$repo_log"
|
||||
export TEST_TMP="$tmp_dir"
|
||||
|
||||
go_xcat_load_functions add_repo_by_url_yum_or_zypper
|
||||
|
||||
TMP_DIR="$TEST_TMP"
|
||||
GO_XCAT_DEFAULT_INSTALL_PATH=/install/xcat
|
||||
yum() { :; }
|
||||
add_repo_by_file() { cp "$1" "$REPO_LOG"; }
|
||||
|
||||
add_repo_by_url_yum_or_zypper \
|
||||
https://repo.example.invalid/xcat-dep/common xcat-dep-common optional
|
||||
}
|
||||
|
||||
@test "an available remote common repository is enabled" {
|
||||
local case_dir="${BATS_TEST_TMPDIR}/remote-present"
|
||||
|
||||
run run_common_repository_case "$case_dir" 1 "" latest
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(read_file_or_empty "${case_dir}/download.log")" = "https://repo.example.invalid/yum/latest/xcat-dep/common/repodata/repomd.xml" ]
|
||||
[ "$(read_file_or_empty "${case_dir}/add.log")" = "https://repo.example.invalid/yum/latest/xcat-dep/common xcat-dep-common" ]
|
||||
[ "$(read_file_or_empty "${case_dir}/id.log")" = "xcat-dep xcat-dep-common" ]
|
||||
}
|
||||
|
||||
@test "a release without the remote common repository remains usable" {
|
||||
local case_dir="${BATS_TEST_TMPDIR}/remote-missing"
|
||||
|
||||
run run_common_repository_case "$case_dir" 0 "" 2.18
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(read_file_or_empty "${case_dir}/add.log")" = "" ]
|
||||
[ "$(read_file_or_empty "${case_dir}/id.log")" = "xcat-dep" ]
|
||||
}
|
||||
|
||||
@test "a custom repository file does not guess an unrelated common repository" {
|
||||
local case_dir="${BATS_TEST_TMPDIR}/repo-file"
|
||||
|
||||
run run_common_repository_case "$case_dir" 1 https://repo.example.invalid/custom/xcat-dep.repo latest
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(read_file_or_empty "${case_dir}/download.log")" = "" ]
|
||||
[ "$(read_file_or_empty "${case_dir}/add.log")" = "" ]
|
||||
}
|
||||
|
||||
@test "a local common repository is enabled beside the distribution repository" {
|
||||
local local_root="${BATS_TEST_TMPDIR}/local-repository"
|
||||
local case_dir="${BATS_TEST_TMPDIR}/local-present"
|
||||
mkdir -p "${local_root}/common/repodata"
|
||||
: >"${local_root}/common/repodata/repomd.xml"
|
||||
|
||||
run run_common_repository_case "$case_dir" 0 "$local_root" latest
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(read_file_or_empty "${case_dir}/add.log")" = "${local_root}/common xcat-dep-common" ]
|
||||
}
|
||||
|
||||
@test "the optional common repository template tolerates outages and verifies metadata" {
|
||||
local template_log="${BATS_TEST_TMPDIR}/generated-common.repo"
|
||||
|
||||
run run_template_generation "$BATS_TEST_TMPDIR" "$template_log"
|
||||
[ "$status" -eq 0 ]
|
||||
grep -Fxq 'skip_if_unavailable=1' "$template_log"
|
||||
grep -Fxq 'repo_gpgcheck=1' "$template_log"
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load 'helpers/go_xcat'
|
||||
|
||||
setup()
|
||||
{
|
||||
go_xcat_require_source
|
||||
export CALLS="${BATS_TEST_TMPDIR}/calls"
|
||||
export GO_XCAT_ARCH=x86_64
|
||||
export GO_XCAT_LINUX_DISTRO=rocky
|
||||
export GO_XCAT_LINUX_VERSION=10.2
|
||||
export EPEL_HAS=1
|
||||
export CRB_HAS=1
|
||||
export QUERY_FAIL=0
|
||||
export QUERY_WARNS=0
|
||||
export SOURCE_ONLY=0
|
||||
export ENTRY=check
|
||||
}
|
||||
|
||||
run_el_repo_check()
|
||||
{
|
||||
rm -f "$CALLS"
|
||||
|
||||
go_xcat_load_functions \
|
||||
repo_carries \
|
||||
el_epel_and_crb_check \
|
||||
install_packages_dnf \
|
||||
install_packages_yum
|
||||
|
||||
EL_EPEL_TEST_RPM=perl-Crypt-CBC
|
||||
EL_CRB_TEST_RPM=perl-IO-Tty
|
||||
|
||||
dnf()
|
||||
{
|
||||
echo "$*" >>"$CALLS"
|
||||
if [[ ${QUERY_FAIL:-0} == 1 ]]; then
|
||||
echo "Error: Failed to download metadata for repo 'epel'" >&2
|
||||
return 1
|
||||
fi
|
||||
[[ ${QUERY_WARNS:-0} == 1 ]] && echo "Warning: repository 'extras' metadata is stale" >&2
|
||||
|
||||
local has=0
|
||||
case "$*" in
|
||||
*perl-Crypt-CBC*) has="$EPEL_HAS" ;;
|
||||
*perl-IO-Tty*) has="$CRB_HAS" ;;
|
||||
esac
|
||||
|
||||
[[ ${SOURCE_ONLY:-0} == 1 && "$*" != *"--arch"* ]] && has=1
|
||||
case "$1" in
|
||||
repoquery) [[ $has == 1 ]] && { echo "${@: -1}"; echo "${@: -1}"; }; return 0 ;;
|
||||
list) [[ $has == 1 ]] && return 0; return 1 ;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
yum()
|
||||
{
|
||||
dnf "$@"
|
||||
}
|
||||
|
||||
case "${ENTRY:-check}" in
|
||||
dnf) install_packages_dnf -y xCAT ;;
|
||||
yum) install_packages_yum -y xCAT ;;
|
||||
*) el_epel_and_crb_check dnf ;;
|
||||
esac
|
||||
}
|
||||
|
||||
@test "EL9 with EPEL and CRB passes after probing binary repositories" {
|
||||
export GO_XCAT_LINUX_VERSION=9.5
|
||||
|
||||
run run_el_repo_check
|
||||
[ "$status" -eq 0 ]
|
||||
probes="$(joined_file_lines "$CALLS")"
|
||||
[[ "$probes" =~ perl-Crypt-CBC.*\;.*perl-IO-Tty ]]
|
||||
[[ "$probes" =~ ^repoquery\ ]]
|
||||
[[ "$probes" =~ --arch\ x86_64,noarch ]]
|
||||
}
|
||||
|
||||
@test "EL9 without EPEL stops and names the EL9 release package" {
|
||||
export GO_XCAT_LINUX_VERSION=9.5
|
||||
export EPEL_HAS=0
|
||||
|
||||
run run_el_repo_check
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ epel-release-latest-9\.noarch ]]
|
||||
}
|
||||
|
||||
@test "EL10 with EPEL and CRB passes after probing both repositories" {
|
||||
run run_el_repo_check
|
||||
[ "$status" -eq 0 ]
|
||||
probes="$(joined_file_lines "$CALLS")"
|
||||
[[ "$probes" =~ perl-Crypt-CBC.*\;.*perl-IO-Tty ]]
|
||||
}
|
||||
|
||||
@test "EL10 without EPEL stops with the EL10 EPEL release package" {
|
||||
export EPEL_HAS=0
|
||||
|
||||
run run_el_repo_check
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ requires\ EPEL\ repository ]]
|
||||
[[ "$output" =~ epel-release-latest-10\.noarch ]]
|
||||
}
|
||||
|
||||
@test "EL10 without CRB stops with current CRB guidance" {
|
||||
export GO_XCAT_LINUX_DISTRO=rhel
|
||||
export CRB_HAS=0
|
||||
|
||||
run run_el_repo_check
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ requires\ CRB\ repository ]]
|
||||
[[ "$output" =~ "'dnf update epel-release' and then 'crb enable'" ]]
|
||||
[[ ! "$output" =~ gpgcheck=0|centos-crb|subscription-manager ]]
|
||||
}
|
||||
|
||||
@test "a source repository does not stand in for the binary one" {
|
||||
export EPEL_HAS=0
|
||||
export SOURCE_ONLY=1
|
||||
|
||||
run run_el_repo_check
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ requires\ EPEL\ repository ]]
|
||||
}
|
||||
|
||||
@test "Oracle Linux 10 without CRB names its CodeReady Builder command" {
|
||||
export GO_XCAT_LINUX_DISTRO=ol
|
||||
export GO_XCAT_LINUX_VERSION=10.1
|
||||
export CRB_HAS=0
|
||||
|
||||
run run_el_repo_check
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ dnf\ config-manager\ --enable\ ol10_codeready_builder ]]
|
||||
}
|
||||
|
||||
@test "a failed repository query stops with the package manager error" {
|
||||
export QUERY_FAIL=1
|
||||
|
||||
run run_el_repo_check
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ Failed\ to\ download\ metadata ]]
|
||||
[[ ! "$output" =~ requires\ EPEL\ repository ]]
|
||||
}
|
||||
|
||||
@test "a warning on stderr does not stand in for a package" {
|
||||
export EPEL_HAS=0
|
||||
export QUERY_WARNS=1
|
||||
|
||||
run run_el_repo_check
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ requires\ EPEL\ repository ]]
|
||||
}
|
||||
|
||||
@test "a warning beside a real match does not fail the check" {
|
||||
export QUERY_WARNS=1
|
||||
|
||||
run run_el_repo_check
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "CentOS Stream 10, which reports the major version alone, is checked" {
|
||||
export GO_XCAT_LINUX_DISTRO=centos
|
||||
export GO_XCAT_LINUX_VERSION=10
|
||||
export EPEL_HAS=0
|
||||
export CRB_HAS=0
|
||||
|
||||
run run_el_repo_check
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ requires\ EPEL\ repository ]]
|
||||
}
|
||||
|
||||
@test "EL8 and Fedora are not checked" {
|
||||
export GO_XCAT_LINUX_VERSION=8.10
|
||||
export EPEL_HAS=0
|
||||
export CRB_HAS=0
|
||||
|
||||
run run_el_repo_check
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(joined_file_lines "$CALLS")" = "" ]
|
||||
|
||||
export GO_XCAT_LINUX_DISTRO=fedora
|
||||
export GO_XCAT_LINUX_VERSION=42
|
||||
|
||||
run run_el_repo_check
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "dnf and yum installer paths run the check before installing" {
|
||||
for entry in dnf yum; do
|
||||
export ENTRY="$entry"
|
||||
export EPEL_HAS=0
|
||||
export CRB_HAS=1
|
||||
|
||||
run run_el_repo_check
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ requires\ EPEL\ repository ]]
|
||||
probes="$(joined_file_lines "$CALLS")"
|
||||
[[ ! "$probes" =~ install ]]
|
||||
|
||||
export EPEL_HAS=1
|
||||
export CRB_HAS=1
|
||||
|
||||
run run_el_repo_check
|
||||
[ "$status" -eq 0 ]
|
||||
probes="$(joined_file_lines "$CALLS")"
|
||||
[[ "$probes" =~ perl-IO-Tty.*\;.*install\ initscripts.*\;.*install\ xCAT ]]
|
||||
done
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
go_xcat_default_source()
|
||||
{
|
||||
printf '%s\n' "${BATS_TEST_DIRNAME}/../../xCAT-server/share/xcat/tools/go-xcat"
|
||||
}
|
||||
|
||||
go_xcat_require_source()
|
||||
{
|
||||
GO_XCAT_SOURCE="${XCAT_TEST_GO_XCAT:-$(go_xcat_default_source)}"
|
||||
export GO_XCAT_SOURCE
|
||||
[ -r "$GO_XCAT_SOURCE" ] || skip "$GO_XCAT_SOURCE is required"
|
||||
}
|
||||
|
||||
go_xcat_extract_functions()
|
||||
{
|
||||
local function_name
|
||||
for function_name in "$@"; do
|
||||
awk -v name="$function_name" '
|
||||
$0 == "function " name "()" { copy = 1 }
|
||||
copy { print }
|
||||
copy && /^}$/ { exit }
|
||||
' "$GO_XCAT_SOURCE"
|
||||
done
|
||||
}
|
||||
|
||||
go_xcat_load_functions()
|
||||
{
|
||||
local function_body function_name
|
||||
function_body="$(go_xcat_extract_functions "$@")" || return 1
|
||||
eval "$function_body"
|
||||
for function_name in "$@"; do
|
||||
declare -F "$function_name" >/dev/null || {
|
||||
printf 'missing %s\n' "$function_name" >&2
|
||||
return 70
|
||||
}
|
||||
done
|
||||
}
|
||||
|
||||
read_file_or_empty()
|
||||
{
|
||||
local path="$1"
|
||||
[ -f "$path" ] || return 0
|
||||
cat "$path"
|
||||
}
|
||||
|
||||
joined_file_lines()
|
||||
{
|
||||
local path="$1"
|
||||
local line separator=""
|
||||
[ -f "$path" ] || return 0
|
||||
while IFS= read -r line; do
|
||||
printf '%s%s' "$separator" "$line"
|
||||
separator=";"
|
||||
done <"$path"
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
repo_root()
|
||||
{
|
||||
printf '%s\n' "${BATS_TEST_DIRNAME}/../.."
|
||||
}
|
||||
|
||||
repo_path()
|
||||
{
|
||||
printf '%s/%s\n' "$(repo_root)" "$1"
|
||||
}
|
||||
|
||||
require_repo_file()
|
||||
{
|
||||
local path
|
||||
path="$(repo_path "$1")"
|
||||
[ -r "$path" ] || skip "$path is required"
|
||||
printf '%s\n' "$path"
|
||||
}
|
||||
|
||||
read_file_or_empty()
|
||||
{
|
||||
local path="$1"
|
||||
[ -f "$path" ] || return 0
|
||||
cat "$path"
|
||||
}
|
||||
|
||||
extract_shell_function()
|
||||
{
|
||||
local file="$1"
|
||||
local name="$2"
|
||||
|
||||
awk -v name="$name" '
|
||||
BEGIN {
|
||||
signature = "^[[:space:]]*(function[[:space:]]+)?" name "([[:space:]]*\\(\\))?[[:space:]]*$"
|
||||
inline_signature = "^[[:space:]]*(function[[:space:]]+)?" name "([[:space:]]*\\(\\))?[[:space:]]*\\{"
|
||||
}
|
||||
$0 ~ signature || $0 ~ inline_signature {
|
||||
copy = 1
|
||||
}
|
||||
copy {
|
||||
print
|
||||
opened += gsub(/\{/, "{")
|
||||
closed += gsub(/\}/, "}")
|
||||
if (opened > 0 && opened == closed) {
|
||||
found = 1
|
||||
exit
|
||||
}
|
||||
}
|
||||
END {
|
||||
if (!found) {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
' "$file"
|
||||
}
|
||||
|
||||
extract_shell_if_block()
|
||||
{
|
||||
local file="$1"
|
||||
local start="$2"
|
||||
|
||||
awk -v start="$start" '
|
||||
index($0, start) {
|
||||
copy = 1
|
||||
}
|
||||
copy {
|
||||
print
|
||||
if ($0 ~ /^[[:space:]]*if[[:space:]\[]/) {
|
||||
depth++
|
||||
}
|
||||
line = $0
|
||||
while (line ~ /(^|[;[:space:]])fi([;[:space:]]|$)/) {
|
||||
depth--
|
||||
sub(/(^|[;[:space:]])fi([;[:space:]]|$)/, " ", line)
|
||||
}
|
||||
if (depth == 0) {
|
||||
found = 1
|
||||
exit
|
||||
}
|
||||
}
|
||||
END {
|
||||
if (!found) {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
' "$file"
|
||||
}
|
||||
|
||||
extract_line_range()
|
||||
{
|
||||
local file="$1"
|
||||
local start="$2"
|
||||
local end="$3"
|
||||
|
||||
awk -v start="$start" -v end="$end" '
|
||||
$0 ~ start {
|
||||
copy = 1
|
||||
}
|
||||
copy {
|
||||
print
|
||||
if ($0 ~ end) {
|
||||
found = 1
|
||||
exit
|
||||
}
|
||||
}
|
||||
END {
|
||||
if (!found) {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
' "$file"
|
||||
}
|
||||
|
||||
extract_first_matching_line()
|
||||
{
|
||||
local file="$1"
|
||||
local pattern="$2"
|
||||
|
||||
awk -v pattern="$pattern" '
|
||||
$0 ~ pattern {
|
||||
print
|
||||
found = 1
|
||||
exit
|
||||
}
|
||||
END {
|
||||
if (!found) {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
' "$file"
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
setup()
|
||||
{
|
||||
SCRIPT_LIB="$(repo_path 'xCAT-server/share/xcat/install/scripts/scriptlib')"
|
||||
XCATDSKLSPOST="$(repo_path 'xCAT/postscripts/xcatdsklspost')"
|
||||
[ -r "$SCRIPT_LIB" ] || skip "$SCRIPT_LIB is required"
|
||||
[ -r "$XCATDSKLSPOST" ] || skip "$XCATDSKLSPOST is required"
|
||||
export SCRIPT_LIB XCATDSKLSPOST
|
||||
}
|
||||
|
||||
capture_install_scriptlib_wget()
|
||||
{
|
||||
local wget_log="$1"
|
||||
|
||||
wget()
|
||||
{
|
||||
printf '%s\n' "$*" >"$wget_log"
|
||||
return 0
|
||||
}
|
||||
|
||||
source "$SCRIPT_LIB"
|
||||
xcat_download_postscripts "192.0.2.10:80" "/install" "/xcatpost" "$wget_log"
|
||||
}
|
||||
|
||||
capture_xcatdsklspost_wget()
|
||||
{
|
||||
local wget_log="$1"
|
||||
local host_init_log="${BATS_TEST_TMPDIR}/host-init.log"
|
||||
local download_log="${BATS_TEST_TMPDIR}/xcatdsklspost-wget-errors.log"
|
||||
|
||||
cat() { printf 'cat %s\n' "$*" >>"$host_init_log"; return 1; }
|
||||
grep()
|
||||
{
|
||||
printf 'grep %s\n' "$*" >>"$host_init_log"
|
||||
command grep "$@"
|
||||
}
|
||||
dirname() { printf 'dirname %s\n' "$*" >>"$host_init_log"; return 1; }
|
||||
|
||||
XCATDSKLSPOST_SOURCE_ONLY=1
|
||||
XCAT_WGET_LOG="$download_log"
|
||||
source "$XCATDSKLSPOST"
|
||||
[ ! -e "$host_init_log" ] || return 1
|
||||
[ "$XCAT_WGET_LOG" = "$download_log" ] || return 1
|
||||
unset -f cat grep dirname
|
||||
|
||||
xcatpost="${BATS_TEST_TMPDIR}/xcatpost"
|
||||
INSTALLDIR=/install
|
||||
echolog() { :; }
|
||||
sleep() { :; }
|
||||
wget()
|
||||
{
|
||||
printf '%s\n' "$*" >"$wget_log"
|
||||
printf '%s\n' 'mock wget stderr' >&2
|
||||
return 0
|
||||
}
|
||||
|
||||
download_postscripts 192.0.2.10:80
|
||||
[ "$(read_file_or_empty "$download_log")" = "mock wget stderr" ]
|
||||
}
|
||||
|
||||
assert_download_policy()
|
||||
{
|
||||
local args="$1"
|
||||
|
||||
[[ "$args" == *'--reject index.html*,post.xcat.ng,post.xcat.rhels10'* ]]
|
||||
[[ "$args" == *'--no-parent'* ]]
|
||||
[[ "$args" == *'postscripts/'* ]]
|
||||
[[ "$args" != *'<a href='* ]]
|
||||
}
|
||||
|
||||
@test "install scriptlib recursive download rejects dispatcher scripts" {
|
||||
local wget_log="${BATS_TEST_TMPDIR}/post-xcat-wget.log"
|
||||
|
||||
run capture_install_scriptlib_wget "$wget_log"
|
||||
[ "$status" -eq 0 ]
|
||||
assert_download_policy "$(read_file_or_empty "$wget_log")"
|
||||
}
|
||||
|
||||
@test "xcatdsklspost recursive download rejects dispatcher scripts" {
|
||||
local wget_log="${BATS_TEST_TMPDIR}/xcatdsklspost-wget.log"
|
||||
|
||||
run capture_xcatdsklspost_wget "$wget_log"
|
||||
[ "$status" -eq 0 ]
|
||||
assert_download_policy "$(read_file_or_empty "$wget_log")"
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
setup()
|
||||
{
|
||||
PKGUTILS="$(repo_path 'xCAT/postscripts/xcatpkgutils.sh')"
|
||||
OSPKGS="$(repo_path 'xCAT/postscripts/ospkgs')"
|
||||
OTHERPKGS="$(repo_path 'xCAT/postscripts/otherpkgs')"
|
||||
[ -r "$PKGUTILS" ] || skip "$PKGUTILS is required"
|
||||
[ -r "$OSPKGS" ] || skip "$OSPKGS is required"
|
||||
[ -r "$OTHERPKGS" ] || skip "$OTHERPKGS is required"
|
||||
APT_LOG="${BATS_TEST_TMPDIR}/apt-get.log"
|
||||
export PKGUTILS OSPKGS OTHERPKGS APT_LOG
|
||||
}
|
||||
|
||||
# Every apt-get call is recorded as "<DEBIAN_FRONTEND>|<arguments>" and answers with APT_STATUS.
|
||||
shadow_apt_get()
|
||||
{
|
||||
apt-get()
|
||||
{
|
||||
printf '%s|%s\n' "${DEBIAN_FRONTEND:-unset}" "$*" >>"$APT_LOG"
|
||||
return "${APT_STATUS:-0}"
|
||||
}
|
||||
}
|
||||
|
||||
apt_call()
|
||||
{
|
||||
sed -n "${1}p" "$APT_LOG"
|
||||
}
|
||||
|
||||
apt_calls()
|
||||
{
|
||||
wc -l <"$APT_LOG" | tr -d ' '
|
||||
}
|
||||
|
||||
run_ospkgs_apt_block()
|
||||
{
|
||||
local block
|
||||
block="$(extract_line_range "$OSPKGS" '# upgrade existing packages' '# remove packages')" || return 99
|
||||
local ENVLIST="" groups="" pkgs=" foo bar" cudapkgs="${1:-}" RETURNVAL=0 ARCH=x86_64
|
||||
eval "$block"
|
||||
printf 'RETURNVAL=%s\n' "$RETURNVAL"
|
||||
}
|
||||
|
||||
run_otherpkgs_apt_line()
|
||||
{
|
||||
local line
|
||||
line="$(extract_first_matching_line "$OTHERPKGS" "$1")" || return 99
|
||||
local envlist="" repo_pkgs="foo bar" result=""
|
||||
eval "$line"
|
||||
printf 'R=%s\n' "$?"
|
||||
printf '%s\n' "$result"
|
||||
}
|
||||
|
||||
@test "the postscripts and the package utilities ship executable" {
|
||||
[ -x "$OSPKGS" ]
|
||||
[ -x "$OTHERPKGS" ]
|
||||
[ -x "$PKGUTILS" ]
|
||||
}
|
||||
|
||||
@test "xcat_apt_get runs apt-get unattended and accepts the unsigned xCAT repositories" {
|
||||
source "$PKGUTILS"
|
||||
shadow_apt_get
|
||||
|
||||
run xcat_apt_get -q install --no-install-recommends foo bar
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(apt_call 1)" = "noninteractive|-y --allow-unauthenticated -q install --no-install-recommends foo bar" ]
|
||||
[ "$(apt_calls)" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "xcat_apt_get returns the apt-get status" {
|
||||
source "$PKGUTILS"
|
||||
shadow_apt_get
|
||||
|
||||
APT_STATUS=100 run xcat_apt_get upgrade
|
||||
[ "$status" -eq 100 ]
|
||||
}
|
||||
|
||||
@test "a pkglist environment prefix reaches apt-get through the eval the postscripts use" {
|
||||
source "$PKGUTILS"
|
||||
apt-get()
|
||||
{
|
||||
printf '%s\n' "${ACCEPT_EULA:-unset}" >>"$APT_LOG"
|
||||
}
|
||||
local ENVLIST="ACCEPT_EULA=y"
|
||||
|
||||
run eval "$ENVLIST xcat_apt_get -q install foo"
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(apt_call 1)" = "y" ]
|
||||
}
|
||||
|
||||
@test "ospkgs upgrades and installs through xcat_apt_get without --force-yes" {
|
||||
source "$PKGUTILS"
|
||||
shadow_apt_get
|
||||
|
||||
run run_ospkgs_apt_block
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *'RETURNVAL=0'* ]]
|
||||
[ "$(apt_call 1 | cut -d'|' -f2)" = "-y update" ]
|
||||
[ "$(apt_call 2)" = "noninteractive|-y --allow-unauthenticated -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef upgrade" ]
|
||||
[ "$(apt_call 3)" = "noninteractive|-y --allow-unauthenticated -q install --no-install-recommends foo bar" ]
|
||||
[ "$(apt_calls)" -eq 3 ]
|
||||
! grep -q -- '--force-yes' "$APT_LOG"
|
||||
}
|
||||
|
||||
@test "ospkgs keeps the apt-get failure status and still runs the later steps" {
|
||||
source "$PKGUTILS"
|
||||
shadow_apt_get
|
||||
|
||||
APT_STATUS=100 run run_ospkgs_apt_block
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *'RETURNVAL=100'* ]]
|
||||
[ "$(apt_calls)" -eq 3 ]
|
||||
}
|
||||
|
||||
@test "ospkgs reports a failed cuda package install" {
|
||||
source "$PKGUTILS"
|
||||
apt-get()
|
||||
{
|
||||
printf '%s|%s\n' "${DEBIAN_FRONTEND:-unset}" "$*" >>"$APT_LOG"
|
||||
case "$*" in
|
||||
*cuda*) return 100 ;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
run run_ospkgs_apt_block " cuda-toolkit"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *'RETURNVAL=100'* ]]
|
||||
[ "$(apt_call 4)" = "noninteractive|-y --allow-unauthenticated -q install --no-install-recommends cuda-toolkit" ]
|
||||
[ "$(apt_calls)" -eq 4 ]
|
||||
}
|
||||
|
||||
run_ospkgs_rpm_cuda_block()
|
||||
{
|
||||
local block tail="${BATS_TEST_TMPDIR}/ospkgs-rpm-tail"
|
||||
sed -n '/#install cuda package if any/,$p' "$OSPKGS" >"$tail"
|
||||
block="$(extract_shell_if_block "$tail" 'if [ -n "$cudapkgs" ]; then')" || return 99
|
||||
local ENVLIST="" yumcmd=fake_dnf cudapkgs=" cuda-toolkit" RETURNVAL=0 ARCH=x86_64 debug=0 log_label=ospkgs
|
||||
logger() { :; }
|
||||
fake_dnf()
|
||||
{
|
||||
printf '%s\n' "$*" >>"$APT_LOG"
|
||||
return 100
|
||||
}
|
||||
eval "$block"
|
||||
printf 'RETURNVAL=%s\n' "$RETURNVAL"
|
||||
}
|
||||
|
||||
@test "ospkgs reports a failed cuda package install on yum and dnf nodes" {
|
||||
run run_ospkgs_rpm_cuda_block
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *'RETURNVAL=100'* ]]
|
||||
[ "$(apt_call 1)" = "-y install cuda-toolkit" ]
|
||||
[ "$(apt_calls)" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "otherpkgs upgrades through xcat_apt_get without --force-yes" {
|
||||
source "$PKGUTILS"
|
||||
shadow_apt_get
|
||||
|
||||
run run_otherpkgs_apt_line 'result=`eval [$]envlist .*Dpkg::Options.* upgrade 2>&1`'
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *'R=0'* ]]
|
||||
[ "$(apt_call 1)" = "noninteractive|-y --allow-unauthenticated -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef upgrade" ]
|
||||
[ "$(apt_calls)" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "otherpkgs installs through xcat_apt_get without --force-yes" {
|
||||
source "$PKGUTILS"
|
||||
shadow_apt_get
|
||||
|
||||
run run_otherpkgs_apt_line 'result=`eval [$]envlist .*Dpkg::Options.* install [$]repo_pkgs 2>&1`'
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *'R=0'* ]]
|
||||
[ "$(apt_call 1)" = "noninteractive|-y --allow-unauthenticated -q -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef install foo bar" ]
|
||||
[ "$(apt_calls)" -eq 1 ]
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
bats_require_minimum_version 1.5.0
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
setup()
|
||||
{
|
||||
XCATLIB="$(repo_path 'xCAT/postscripts/xcatlib.sh')"
|
||||
[ -r "$XCATLIB" ] || skip "$XCATLIB is required"
|
||||
export XCATLIB
|
||||
}
|
||||
|
||||
run_restart_fallback()
|
||||
{
|
||||
local poll_count=0
|
||||
|
||||
ps()
|
||||
{
|
||||
cat <<'EOF'
|
||||
root 4321 0.0 0.0 15432 2048 ? Ss 10:00 0:00 /usr/sbin/sshd
|
||||
EOF
|
||||
}
|
||||
kill()
|
||||
{
|
||||
if [ "$1" = "-9" ]; then
|
||||
printf 'kill %s\n' "$*" >>"$EVENT_LOG"
|
||||
return 0
|
||||
fi
|
||||
|
||||
poll_count=$((poll_count + 1))
|
||||
if [ "$poll_count" -eq 1 ]; then
|
||||
printf 'poll %s alive\n' "$2" >>"$EVENT_LOG"
|
||||
return 0
|
||||
fi
|
||||
printf 'poll %s gone\n' "$2" >>"$EVENT_LOG"
|
||||
return 1
|
||||
}
|
||||
sleep() { :; }
|
||||
sshd()
|
||||
{
|
||||
printf '%s\n' start >>"$EVENT_LOG"
|
||||
}
|
||||
|
||||
source "$XCATLIB"
|
||||
xcat_restart_sshd_after_failed_service_restart sshd
|
||||
}
|
||||
|
||||
run_wait_for_processes()
|
||||
{
|
||||
local rc
|
||||
|
||||
source "$XCATLIB"
|
||||
xcat_wait_for_processes_to_exit "$*" 3
|
||||
rc=$?
|
||||
printf '%s\n' "$rc"
|
||||
return 0
|
||||
}
|
||||
|
||||
@test "remoteshell restart fallback kills, waits, then starts sshd" {
|
||||
EVENT_LOG="${BATS_TEST_TMPDIR}/events.log"
|
||||
export EVENT_LOG
|
||||
|
||||
run run_restart_fallback
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(read_file_or_empty "$EVENT_LOG")" = $'kill -9 4321\npoll 4321 alive\npoll 4321 gone\nstart' ]
|
||||
run -1 grep -Eq '^kill 9( |$)' "$EVENT_LOG"
|
||||
}
|
||||
|
||||
@test "remoteshell wait loop reports a still-running process and gives up" {
|
||||
local child
|
||||
|
||||
sleep 30 &
|
||||
child=$!
|
||||
|
||||
run run_wait_for_processes "$child"
|
||||
kill -KILL "$child" 2>/dev/null || true
|
||||
wait "$child" 2>/dev/null || true
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "1" ]
|
||||
}
|
||||
|
||||
@test "remoteshell wait loop returns as soon as killed processes are gone" {
|
||||
run run_wait_for_processes 999999
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "0" ]
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
bats_require_minimum_version 1.5.0
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
setup()
|
||||
{
|
||||
SCRIPT_LIB="$(repo_path 'xCAT-server/share/xcat/install/scripts/scriptlib')"
|
||||
[ -r "$SCRIPT_LIB" ] || skip "$SCRIPT_LIB is required"
|
||||
export SCRIPT_LIB
|
||||
}
|
||||
|
||||
@test "SLES 11 UEFI install changes the AutoYaST bootloader to elilo" {
|
||||
local cmdline="${BATS_TEST_TMPDIR}/cmdline"
|
||||
local profile="${BATS_TEST_TMPDIR}/modified.xml"
|
||||
|
||||
printf '%s\n' 'BOOT_IMAGE=/linux install=http://192.0.2.10/install/sles11/ppc64le' >"$cmdline"
|
||||
cat >"$profile" <<'EOF'
|
||||
<bootloader>
|
||||
<lba_support config:type="boolean">true</lba_support>
|
||||
<linear config:type="boolean">true</linear>
|
||||
<location>mbr</location>
|
||||
</bootloader>
|
||||
EOF
|
||||
|
||||
source "$SCRIPT_LIB"
|
||||
run set_sles11_uefi_bootloader "$cmdline" "$profile"
|
||||
[ "$status" -eq 0 ]
|
||||
grep -Fxq '<loader_type>elilo</loader_type>' "$profile"
|
||||
run -1 grep -q '<location>mbr</location>' "$profile"
|
||||
run -1 grep -q '<lba_support ' "$profile"
|
||||
run -1 grep -q '<linear ' "$profile"
|
||||
}
|
||||
|
||||
@test "non-SLES 11 install media keeps the legacy bootloader template value" {
|
||||
local cmdline="${BATS_TEST_TMPDIR}/cmdline"
|
||||
local profile="${BATS_TEST_TMPDIR}/modified.xml"
|
||||
|
||||
printf '%s\n' 'BOOT_IMAGE=/linux install=http://192.0.2.10/install/sles15/ppc64le' >"$cmdline"
|
||||
printf '%s\n' '<location>mbr</location>' >"$profile"
|
||||
|
||||
source "$SCRIPT_LIB"
|
||||
run set_sles11_uefi_bootloader "$cmdline" "$profile"
|
||||
[ "$status" -eq 0 ]
|
||||
grep -Fxq '<location>mbr</location>' "$profile"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
setup()
|
||||
{
|
||||
ADD_SSH="$(repo_path 'xCAT-server/share/xcat/netboot/add-on/statelite/add_ssh')"
|
||||
[ -r "$ADD_SSH" ] || skip "$ADD_SSH is required"
|
||||
export ADD_SSH
|
||||
}
|
||||
|
||||
run_sshd_config_block()
|
||||
{
|
||||
local root="$1"
|
||||
local block
|
||||
|
||||
block="$(extract_shell_if_block "$ADD_SSH" 'if [ -r $ROOTDIR/etc/ssh/sshd_config ]')" || return 1
|
||||
ROOTDIR="$root"
|
||||
eval "$block"
|
||||
}
|
||||
|
||||
@test "statelite add_ssh writes sshd settings below systemd's open-file limit" {
|
||||
local root="${BATS_TEST_TMPDIR}/rootimg"
|
||||
local sshd_config="${root}/etc/ssh/sshd_config"
|
||||
|
||||
mkdir -p "${root}/etc/ssh"
|
||||
cat >"$sshd_config" <<'EOF'
|
||||
X11Forwarding no
|
||||
KeyRegenerationInterval 3600
|
||||
MaxStartups 1024
|
||||
EOF
|
||||
|
||||
run run_sshd_config_block "$root"
|
||||
[ "$status" -eq 0 ]
|
||||
grep -Fxq 'X11Forwarding yes' "$sshd_config"
|
||||
grep -Fxq 'KeyRegenerationInterval 0' "$sshd_config"
|
||||
grep -Fxq '#MaxStartups 1024' "$sshd_config"
|
||||
grep -Fxq 'MaxStartups 100:30:200' "$sshd_config"
|
||||
! grep -Fxq 'MaxStartups 1024' "$sshd_config"
|
||||
}
|
||||
@@ -46,6 +46,11 @@ so putting a test in `integration/` does not cost it CI coverage. What differs i
|
||||
each suite is allowed to depend on, and that unit tests also run standalone from a
|
||||
bare checkout with no xCAT at all.
|
||||
|
||||
Shell-script unit tests belong in [`../bats`](../bats/README.md)
|
||||
and run with BATS. Do not add Perl `.t` tests that grep shell source when the
|
||||
behavior can be exercised by sourcing a shell library or script and shadowing the
|
||||
external commands it calls.
|
||||
|
||||
The distinction matters because a test that needs an absent environment does not fail
|
||||
-- it calls `plan skip_all` and reports as skipped. A handful of those in a suite of
|
||||
several hundred assertions is easy to stop reading. Keeping the two kinds in separate
|
||||
|
||||
@@ -60,15 +60,19 @@ is_deeply( [deb_package_arches('perl-xCAT')], ['all'],
|
||||
'a Perl package is built once, arch-independent' );
|
||||
is_deeply( [deb_package_arches('xCAT-probe')], ['all'],
|
||||
'xCAT-probe is arch-independent too' );
|
||||
for my $pkg (qw(xCAT xCATsn xCAT-genesis-scripts)) {
|
||||
is_deeply( [deb_package_arches($pkg)], ['amd64', 'ppc64el'],
|
||||
"$pkg is built per architecture" );
|
||||
for my $pkg (qw(xCAT xCATsn)) {
|
||||
is_deeply( [deb_package_arches($pkg)], ['amd64', 'ppc64el', 'riscv64'],
|
||||
"$pkg is built for every architecture a management node runs on" );
|
||||
}
|
||||
# xCAT-genesis-scripts has one control file per architecture and there is no riscv64 one,
|
||||
# so asking for that build would stop the whole run.
|
||||
is_deeply( [deb_package_arches('xCAT-genesis-scripts')], ['amd64', 'ppc64el'],
|
||||
'xCAT-genesis-scripts is built only for the architectures it has a control file for' );
|
||||
is_deeply( [deb_package_arches(undef)], ['all'],
|
||||
'an undefined package name does not blow up the arch lookup' );
|
||||
|
||||
is_deeply( [dist_arches('noble')], ['amd64', 'ppc64el'],
|
||||
'a current release serves both architectures' );
|
||||
is_deeply( [dist_arches('noble')], ['amd64', 'ppc64el', 'riscv64'],
|
||||
'a current release serves every architecture' );
|
||||
is_deeply( [dist_arches('saucy')], ['amd64'],
|
||||
'saucy predates ppc64el and serves only amd64' );
|
||||
|
||||
@@ -151,8 +155,8 @@ is( scalar( () = $rewritten =~ /^ -- xCAT Build /mg ), 1,
|
||||
|
||||
my $dists = reprepro_distributions([qw(focal noble)], 'DEADBEEF');
|
||||
is( scalar(() = $dists =~ /^Codename:/mg), 2, 'one stanza per release' );
|
||||
like( $dists, qr/^Codename: focal\nArchitectures: amd64 ppc64el$/m,
|
||||
'a release declares both architectures, on the line after its codename' );
|
||||
like( $dists, qr/^Codename: focal\nArchitectures: amd64 ppc64el riscv64$/m,
|
||||
'a release declares every architecture, on the line after its codename' );
|
||||
is( scalar(() = $dists =~ /^SignWith: DEADBEEF$/mg), 2,
|
||||
'every stanza is signed when a key is given' );
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Spec;
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# A riscv64 management node needs an xcat and xcatsn deb built for the architecture and a
|
||||
# mklocalrepo.sh that points the host at the matching repository instead of amd64.
|
||||
#
|
||||
# The generated script is extracted from builddebs.pl and run with a stub uname ahead of
|
||||
# $PATH, so the mapping under test is the shipped code. Only the path it writes is
|
||||
# redirected into the sandbox, because it writes an apt source list.
|
||||
|
||||
my $repo_root = File::Spec->rel2abs( File::Spec->catdir( $FindBin::Bin, '..', '..' ) );
|
||||
my $builder = File::Spec->catfile( $repo_root, 'builddebs.pl' );
|
||||
plan skip_all => "builddebs.pl not found" unless -f $builder;
|
||||
|
||||
my $src = do { local $/; open my $fh, '<', $builder or die $!; <$fh> };
|
||||
|
||||
# BAIL_OUT rather than skip: a rename that stops this matching must fail loudly instead of
|
||||
# silently covering nothing.
|
||||
my ($script) = $src =~ /write_script\("\$repodir\/mklocalrepo\.sh", <<'SCRIPT'\);\n(.*?)\nSCRIPT\n/ms;
|
||||
BAIL_OUT('could not extract mklocalrepo.sh from builddebs.pl') unless defined $script;
|
||||
|
||||
my $dir = tempdir( CLEANUP => 1 );
|
||||
my $run = 0;
|
||||
|
||||
# Run the generated script for one host architecture and return the apt source line it wrote.
|
||||
sub sources_line_for {
|
||||
my ($uname) = @_;
|
||||
$run++;
|
||||
my $root = File::Spec->catdir( $dir, "run$run" );
|
||||
mkdir $root;
|
||||
mkdir "$root/bin";
|
||||
|
||||
open( my $stub, '>', "$root/bin/uname" ) or die $!;
|
||||
print {$stub} "#!/bin/bash\necho $uname\n";
|
||||
close($stub);
|
||||
chmod 0755, "$root/bin/uname";
|
||||
|
||||
my $release = File::Spec->catfile( $root, 'lsb-release' );
|
||||
open( my $rel, '>', $release ) or die $!;
|
||||
print {$rel} "DISTRIB_CODENAME=noble\n";
|
||||
close($rel);
|
||||
|
||||
my $listed = File::Spec->catfile( $root, 'sources.list' );
|
||||
( my $sandboxed = $script ) =~ s{/etc/lsb-release}{$release};
|
||||
$sandboxed =~ s{/etc/apt/sources\.list\.d/\S+}{$listed};
|
||||
|
||||
my $harness = File::Spec->catfile( $root, 'harness.sh' );
|
||||
open( my $fh, '>', $harness ) or die $!;
|
||||
print {$fh} "#!/bin/bash\n$sandboxed\n";
|
||||
close($fh);
|
||||
|
||||
local $ENV{PATH} = "$root/bin:$ENV{PATH}";
|
||||
system( '/bin/bash', $harness );
|
||||
open( my $out, '<', $listed ) or die $!;
|
||||
my $line = do { local $/; <$out> };
|
||||
close($out);
|
||||
return $line;
|
||||
}
|
||||
|
||||
for my $case (
|
||||
[ 'riscv64', 'riscv64' ],
|
||||
[ 'ppc64le', 'ppc64el' ],
|
||||
[ 'x86_64', 'amd64' ],
|
||||
)
|
||||
{
|
||||
my ( $uname, $want ) = @$case;
|
||||
like( sources_line_for($uname), qr/^deb \[arch=\Q$want\E\] /,
|
||||
"mklocalrepo.sh gives a $uname host the $want repository" );
|
||||
}
|
||||
|
||||
# The debs themselves must exist for the architecture.
|
||||
for my $case ( [ 'xCAT', 'xcat' ], [ 'xCATsn', 'xcatsn' ] ) {
|
||||
my ( $component, $package ) = @$case;
|
||||
my $control = File::Spec->catfile( $repo_root, $component, 'debian', 'control' );
|
||||
my $text = do { local $/; open my $fh, '<', $control or die $!; <$fh> };
|
||||
my ($arches) = $text =~ /^Package: \Q$package\E\nArchitecture: (.*)$/m;
|
||||
ok( defined $arches, "$package declares an architecture" );
|
||||
like( $arches || '', qr/\briscv64\b/, "$package is built for riscv64" );
|
||||
like( $arches || '', qr/\bamd64\b.*\bppc64el\b/, "$package keeps amd64 and ppc64el" );
|
||||
}
|
||||
|
||||
done_testing();
|
||||
@@ -16,6 +16,11 @@ use xCAT::Utils;
|
||||
# --- what debootstrap and the package lists are given ----------------------
|
||||
is(xCAT::Utils->debian_arch('x86_64'), 'amd64',
|
||||
'Debian calls x86_64 amd64');
|
||||
# genimage passes this value to debootstrap --arch and reads it to pick the apt mirror, and
|
||||
# debootstrap knows i386, not xCAT's x86.
|
||||
is(xCAT::Utils->debian_arch('x86'), 'i386',
|
||||
'debian_arch: the 32-bit x86 token becomes i386');
|
||||
|
||||
is(xCAT::Utils->debian_arch('ppc64el'), 'ppc64el',
|
||||
'the Debian name for POWER LE is unchanged');
|
||||
is(xCAT::Utils->debian_arch('ppc64le'), 'ppc64le',
|
||||
@@ -38,6 +43,10 @@ is(xCAT::Utils->xcat_arch_from_debian('ppc64el'), 'ppc64el',
|
||||
'POWER LE media keeps the Debian name xCAT uses for Ubuntu');
|
||||
is(xCAT::Utils->xcat_arch_from_debian('powerpc'), 'ppc64',
|
||||
'POWER BE media installs ppc64 nodes');
|
||||
is(xCAT::Utils->xcat_arch_from_debian('riscv64'), 'riscv64',
|
||||
'riscv64 media installs riscv64 nodes');
|
||||
is(xCAT::Utils->debian_arch('riscv64'), 'riscv64',
|
||||
'Debian and xCAT agree on the riscv64 name');
|
||||
is(xCAT::Utils->xcat_arch_from_debian('nonesuch'), undef,
|
||||
'media xCAT has no name for resolves to nothing');
|
||||
is(xCAT::Utils->xcat_arch_from_debian(''), undef,
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Spec;
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# dh_perl adds no module dependencies, unlike the rpm generator, so a deb declares only what its
|
||||
# control file names. perl-xcat declared libhtml-form-perl alone while its files load nine other
|
||||
# modules at top level, xcat-server shipped the REST API without CGI, xcat-client ships z/VM
|
||||
# helpers that load Capture::Tiny, and nmap and ipmitool-xcat were recommendations where the rpm
|
||||
# packages require them.
|
||||
|
||||
my $repo_root = File::Spec->rel2abs( File::Spec->catdir( $FindBin::Bin, '..', '..' ) );
|
||||
|
||||
# The relation fields of one binary package stanza as sets of package names, plus the version
|
||||
# bound of each relation that has one.
|
||||
sub relations_of {
|
||||
my ( $source, $package ) = @_;
|
||||
my $file = File::Spec->catfile( $repo_root, $source, 'debian', 'control' );
|
||||
open( my $fh, '<', $file ) or die "Unable to read $file: $!";
|
||||
my $control = do { local $/; <$fh> };
|
||||
close($fh);
|
||||
my ($stanza) = $control =~ /^Package:\s*\Q$package\E\s*\n(.*?)(?:\n\n|\z)/ms;
|
||||
die "no stanza for $package in $file" unless defined $stanza;
|
||||
my %rel;
|
||||
foreach my $field (qw(Depends Recommends)) {
|
||||
my ($line) = $stanza =~ /^$field:\s*(.*)$/m;
|
||||
foreach my $entry ( split( /[,|]/, $line // '' ) ) {
|
||||
my ( $name, $bound ) = $entry =~ /^\s*(\S+?)\s*(?:\(([^)]*)\))?\s*(?:\[[^\]]*\])?\s*$/;
|
||||
next unless defined $name;
|
||||
$rel{$field}{$name} = 1;
|
||||
$rel{version}{$name} = $bound if defined $bound;
|
||||
}
|
||||
}
|
||||
return \%rel;
|
||||
}
|
||||
|
||||
sub depends_on {
|
||||
my ( $rel, $name, $label ) = @_;
|
||||
ok( $rel->{Depends}{$name}, "$label depends on $name" );
|
||||
ok( !$rel->{Recommends}{$name}, "... and no longer only recommends it" ) if $rel->{Recommends}{$name};
|
||||
}
|
||||
|
||||
my $perl_xcat = relations_of( 'perl-xCAT', 'perl-xcat' );
|
||||
depends_on( $perl_xcat, $_, 'perl-xcat' ) for qw(
|
||||
libhtml-form-perl libxml-simple-perl libxml-parser-perl libio-socket-ssl-perl libdbi-perl libjson-perl
|
||||
libwww-perl libxml-libxml-perl libexpect-perl libsnmp-perl libsocket6-perl libio-socket-inet6-perl
|
||||
);
|
||||
|
||||
my $client = relations_of( 'xCAT-client', 'xcat-client' );
|
||||
depends_on( $client, 'libcapture-tiny-perl', 'xcat-client' );
|
||||
|
||||
my $server = relations_of( 'xCAT-server', 'xcat-server' );
|
||||
depends_on( $server, 'libcgi-pm-perl', 'xcat-server' );
|
||||
|
||||
# nmap and ipmitool-xcat are hard requirements on EL and were only recommended here.
|
||||
my $xcat = relations_of( 'xCAT', 'xcat' );
|
||||
my $xcatsn = relations_of( 'xCATsn', 'xcatsn' );
|
||||
foreach my $case ( [ $xcat, 'xcat' ], [ $xcatsn, 'xcatsn' ], [ $client, 'xcat-client' ] ) {
|
||||
my ( $rel, $label ) = @$case;
|
||||
ok( $rel->{Depends}{nmap}, "$label depends on nmap" );
|
||||
ok( !$rel->{Recommends}{nmap}, "... and does not recommend it as well" );
|
||||
}
|
||||
foreach my $case ( [ $xcat, 'xcat' ], [ $xcatsn, 'xcatsn' ] ) {
|
||||
my ( $rel, $label ) = @$case;
|
||||
ok( $rel->{Depends}{'ipmitool-xcat'}, "$label depends on ipmitool-xcat" );
|
||||
ok( !$rel->{Recommends}{'ipmitool-xcat'}, "... and does not recommend it as well" );
|
||||
is( $rel->{version}{'ipmitool-xcat'}, '>= 1.8.18-4', "... at the floor xCAT.spec requires" );
|
||||
}
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Path qw(make_path);
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# mkinstall logged "Unknown arch" for every architecture other than x86_64, x86, ppc64le and
|
||||
# ppc64el, so each diskful riscv64 install produced a false message. install_darch names the
|
||||
# architectures xCAT installs Ubuntu on and the Debian name each one maps to.
|
||||
|
||||
# xCAT modules put $::XCATROOT/lib/perl ahead of @INC as they compile, so on a host with xCAT
|
||||
# installed the modules loaded after the first one would come from /opt/xcat. XCATROOT points
|
||||
# at this checkout before any of them compiles.
|
||||
BEGIN {
|
||||
my $root = tempdir( CLEANUP => 1 );
|
||||
make_path("$root/lib");
|
||||
symlink( "$FindBin::Bin/../../perl-xCAT", "$root/lib/perl" ) or die "symlink: $!";
|
||||
$ENV{XCATROOT} = $root;
|
||||
}
|
||||
|
||||
BEGIN {
|
||||
package xCAT::TableUtils;
|
||||
our $tftpdir;
|
||||
sub getTftpDir { return $tftpdir; }
|
||||
$INC{'xCAT/TableUtils.pm'} = __FILE__;
|
||||
}
|
||||
|
||||
use lib "$FindBin::Bin/../../perl-xCAT";
|
||||
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
|
||||
my $plugin = "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/debian.pm";
|
||||
require $plugin;
|
||||
|
||||
my @cases = (
|
||||
# arch Debian name installs Ubuntu
|
||||
[ 'x86_64', 'amd64', 1 ],
|
||||
[ 'x86', 'i386', 1 ],
|
||||
[ 'ppc64le', 'ppc64le', 1 ],
|
||||
[ 'ppc64el', 'ppc64el', 1 ],
|
||||
[ 'riscv64', 'riscv64', 1 ],
|
||||
[ 'aarch64', 'aarch64', 0 ],
|
||||
[ 'sparc', 'sparc', 0 ],
|
||||
);
|
||||
foreach my $case (@cases) {
|
||||
my ( $arch, $darch, $known ) = @$case;
|
||||
my ( $got_darch, $got_known ) = xCAT_plugin::debian::install_darch($arch);
|
||||
is( $got_darch, $darch, "$arch installs from the $darch tree" );
|
||||
is( $got_known, $known, $known ? "... and is an architecture xCAT installs Ubuntu on" : "... and is reported as unknown" );
|
||||
}
|
||||
|
||||
done_testing();
|
||||
@@ -91,6 +91,18 @@ is(
|
||||
'POWER does not accept the x86 live layout',
|
||||
);
|
||||
|
||||
# --- riscv64 -------------------------------------------------------------
|
||||
is(
|
||||
resolved('riscv64', 'riscv64', media('casper/vmlinux', 'casper/initrd')),
|
||||
'casper/vmlinux|casper/initrd',
|
||||
'the riscv64 live image keeps its kernel under a different name',
|
||||
);
|
||||
is(
|
||||
resolved('riscv64', 'riscv64', media('casper/vmlinuz', 'casper/initrd')),
|
||||
undef,
|
||||
'riscv64 does not accept the kernel name the other live images use',
|
||||
);
|
||||
|
||||
# --- nothing to boot -------------------------------------------------------
|
||||
is(resolved('x86_64', 'amd64', media('casper/vmlinuz')), undef,
|
||||
'a kernel without its initrd is not a match');
|
||||
|
||||
@@ -52,8 +52,8 @@ my $marker = File::Spec->catfile( tempdir( @fixture_dir, CLEANUP => 1 ), 'tcpdum
|
||||
sub fixture_bin {
|
||||
my (%with) = @_;
|
||||
my $bindir = tempdir( @fixture_dir, CLEANUP => 1 );
|
||||
write_script( "$bindir/tcpdump", qq{printf '%s\\n' "\$0" "\$*" > '$marker'\nexit 0\n} );
|
||||
foreach my $tool (qw(awk head grep ps)) {
|
||||
write_script( "$bindir/tcpdump", qq{printf '%s\\n' "\$0" "\$*" > '$marker'\ntrap 'exit 0' TERM\nwhile :; do sleep 1; done\n} );
|
||||
foreach my $tool (qw(awk head grep ps sleep)) {
|
||||
my $real = xCAT::CommandUtils::find_executable($tool) or next;
|
||||
symlink( $real, "$bindir/$tool" ) or die "symlink $tool: $!";
|
||||
}
|
||||
@@ -142,4 +142,136 @@ SKIP: {
|
||||
is( $args, "-i lo port 68 -n -vvvvvv", '... with the capture arguments' );
|
||||
}
|
||||
|
||||
# ---- capture lifecycle: a private dump file, tcpdump run directly and stopped by pid ------------
|
||||
# Both copies wrote /tmp/dhcpdumpfile.log, ran tcpdump behind a shell, killed every tcpdump on the
|
||||
# interface by pattern, and reported zero servers when tcpdump failed to start. The fake tcpdump
|
||||
# below records who started it and where its output goes, waits for the TERM the script owes it,
|
||||
# and a second one fails at once. ps records any use, since the scripts no longer need it.
|
||||
sub lifecycle_bin {
|
||||
my (%with) = @_;
|
||||
my $bindir = tempdir( @fixture_dir, CLEANUP => 1 );
|
||||
my $record = "$with{record}";
|
||||
if ( $with{fail} ) {
|
||||
write_script( "$bindir/tcpdump", qq{printf 'started\\n' >> '$record'\nexit 1\n} );
|
||||
} elsif ( $with{quit} ) {
|
||||
write_script( "$bindir/tcpdump", qq{printf 'started\\n' >> '$record'\nexit 0\n} );
|
||||
} elsif ( $with{killed} ) {
|
||||
write_script( "$bindir/tcpdump", qq{printf 'started\\n' >> '$record'\nkill -9 \$\$\n} );
|
||||
} elsif ( $with{stubborn} ) {
|
||||
write_script( "$bindir/tcpdump", qq{trap '' TERM\nprintf 'started\\n' >> '$record'\nwhile :; do sleep 1; done\n} );
|
||||
} else {
|
||||
write_script( "$bindir/tcpdump",
|
||||
qq{parent=\$(cat /proc/\$PPID/comm 2>/dev/null)\n}
|
||||
. qq{out=\$(readlink /proc/\$\$/fd/1 2>/dev/null)\n}
|
||||
. qq{printf 'pid=%s ppid=%s parent=%s out=%s\\n' "\$\$" "\$PPID" "\$parent" "\$out" >> '$record'\n}
|
||||
. qq{trap 'printf "TERM\\n" >> "$record"; exit 0' TERM\n}
|
||||
. qq{while :; do sleep 1; done\n} );
|
||||
}
|
||||
write_script( "$bindir/ps", qq{printf 'ps %s\\n' "\$*" >> '$record.ps'\nexit 0\n} );
|
||||
foreach my $tool (qw(awk head grep cat readlink sleep)) {
|
||||
my $real = xCAT::CommandUtils::find_executable($tool) or next;
|
||||
symlink( $real, "$bindir/$tool" ) or die "symlink $tool: $!";
|
||||
}
|
||||
symlink( $with{real_ip}, "$bindir/ip" ) or die "symlink ip: $!";
|
||||
return $bindir;
|
||||
}
|
||||
|
||||
sub run_isolated_status {
|
||||
my ( $ns, $bindir, $tmpdir, $script, @args ) = @_;
|
||||
my $command = "env PATH='$bindir' XCATROOT='$root' TMPDIR='$tmpdir' " . script_command( $script, @args );
|
||||
my $shell = "$ns->{unshare} $ns->{flags} sh -c \"$ns->{setup} && exec $command\" 2>&1";
|
||||
my $out = `$shell`;
|
||||
return ( $out, $? >> 8 );
|
||||
}
|
||||
|
||||
sub slurp_lines {
|
||||
my ($path) = @_;
|
||||
open( my $fh, '<', $path ) or return;
|
||||
chomp( my @lines = <$fh> );
|
||||
close($fh);
|
||||
return @lines;
|
||||
}
|
||||
|
||||
SKIP: {
|
||||
skip 'the private /tmp would hide this checkout or its fixtures', 44
|
||||
if index( $repo, '/tmp/' ) == 0 || !@fixture_dir;
|
||||
my $ns = isolation();
|
||||
skip 'no private network namespace on this host', 44 unless $ns;
|
||||
|
||||
foreach my $case ( [ $tools, 'the tool', '-t' ], [ $probe, 'the probe', '-d' ] ) {
|
||||
my ( $script, $label, $window ) = @$case;
|
||||
my $tmpdir = tempdir( @fixture_dir, CLEANUP => 1 );
|
||||
my $record = File::Spec->catfile( tempdir( @fixture_dir, CLEANUP => 1 ), 'tcpdump.record' );
|
||||
my $bindir = lifecycle_bin( record => $record, real_ip => $ns->{ip} );
|
||||
|
||||
my ( $out, $status ) = run_isolated_status( $ns, $bindir, $tmpdir, $script, '-i', 'lo', '-m', $mac, $window, '1' );
|
||||
is( $status, 0, "$label exits 0 after a capture window" );
|
||||
my @rec = slurp_lines($record);
|
||||
my ($start) = grep { /^pid=/ } @rec;
|
||||
ok( defined $start, "$label started tcpdump" ) or diag($out);
|
||||
like( $start // '', qr/ parent=perl /, '... directly from the script, with no shell in between' );
|
||||
like( $start // '', qr{ out=\Q$tmpdir\E/detect_dhcpd\.\w+$}, '... writing a private capture file under TMPDIR' );
|
||||
ok( ( grep { $_ eq 'TERM' } @rec ), '... and stopped it with TERM when the window ended' );
|
||||
ok( !-e "$record.ps", '... without searching the process table' );
|
||||
my @left = glob("$tmpdir/detect_dhcpd.*");
|
||||
is( scalar(@left), 0, '... and removed the capture file on exit' );
|
||||
|
||||
my $failing = lifecycle_bin( record => $record, real_ip => $ns->{ip}, fail => 1 );
|
||||
( $out, $status ) = run_isolated_status( $ns, $failing, $tmpdir, $script, '-i', 'lo', '-m', $mac, $window, '1' );
|
||||
is( $status, 1, "$label exits 1 when tcpdump fails to start" ) or diag($out);
|
||||
like( $out, qr/tcpdump ended before the capture window did, with status 1|Capture the packets by tcpdump/, '... and says the capture failed' );
|
||||
unlike( $out, qr/0 servers repl/, '... instead of reporting zero servers' );
|
||||
@left = glob("$tmpdir/detect_dhcpd.*");
|
||||
is( scalar(@left), 0, '... and leaves no capture file behind' );
|
||||
|
||||
my $quitting = lifecycle_bin( record => $record, real_ip => $ns->{ip}, quit => 1 );
|
||||
( $out, $status ) = run_isolated_status( $ns, $quitting, $tmpdir, $script, '-i', 'lo', '-m', $mac, $window, '1' );
|
||||
is( $status, 1, "$label exits 1 when tcpdump quits early with status 0" ) or diag($out);
|
||||
unlike( $out, qr/0 servers repl/, '... instead of reporting zero servers' );
|
||||
|
||||
my $dying = lifecycle_bin( record => $record, real_ip => $ns->{ip}, killed => 1 );
|
||||
( $out, $status ) = run_isolated_status( $ns, $dying, $tmpdir, $script, '-i', 'lo', '-m', $mac, $window, '1' );
|
||||
is( $status, 1, "$label exits 1 when tcpdump dies on another signal" ) or diag($out);
|
||||
like( $out, qr/on signal 9|Capture the packets by tcpdump/, '... and names the signal or the failed step' );
|
||||
|
||||
my $stubborn = lifecycle_bin( record => $record, real_ip => $ns->{ip}, stubborn => 1 );
|
||||
my $began = time;
|
||||
( $out, $status ) = run_isolated_status( $ns, $stubborn, $tmpdir, $script, '-i', 'lo', '-m', $mac, $window, '1' );
|
||||
is( $status, 1, "$label exits 1 when tcpdump ignores TERM" ) or diag($out);
|
||||
cmp_ok( time - $began, '<', 20, '... after a bounded grace period' );
|
||||
like( $out, qr/on signal 9|Capture the packets by tcpdump/, '... having killed it' );
|
||||
|
||||
# An interrupt while the capture runs: the script is signalled once the fake tcpdump has
|
||||
# reported the script's pid, and must stop tcpdump and remove the file on its way out.
|
||||
my $int_record = File::Spec->catfile( tempdir( @fixture_dir, CLEANUP => 1 ), 'tcpdump.record' );
|
||||
my $int_bin = lifecycle_bin( record => $int_record, real_ip => $ns->{ip} );
|
||||
my $runner = fork;
|
||||
die "fork: $!" unless defined $runner;
|
||||
if ( $runner == 0 ) {
|
||||
my ( undef, $st ) = run_isolated_status( $ns, $int_bin, $tmpdir, $script, '-i', 'lo', '-m', $mac, $window, '30' );
|
||||
exit $st;
|
||||
}
|
||||
my $started;
|
||||
foreach ( 1 .. 150 ) {
|
||||
($started) = grep { /^pid=/ } slurp_lines($int_record);
|
||||
last if $started;
|
||||
select( undef, undef, undef, 0.2 );
|
||||
}
|
||||
my ($tcpdump_pid) = ( $started // '' ) =~ /^pid=(\d+)/;
|
||||
my ($script_pid) = ( $started // '' ) =~ / ppid=(\d+)/;
|
||||
ok( $script_pid, "$label reports the pid to interrupt" ) or diag( $started // 'tcpdump never started' );
|
||||
if ($script_pid) {
|
||||
kill 'INT', $script_pid;
|
||||
select( undef, undef, undef, 0.3 );
|
||||
kill 'INT', $script_pid;
|
||||
}
|
||||
waitpid( $runner, 0 );
|
||||
is( $? >> 8, 1, '... and exits 1 on a double interrupt during the capture' );
|
||||
ok( ( grep { $_ eq 'TERM' } slurp_lines($int_record) ), '... after stopping tcpdump' );
|
||||
ok( !( $tcpdump_pid && kill( 0, $tcpdump_pid ) ), '... which is gone' );
|
||||
@left = glob("$tmpdir/detect_dhcpd.*");
|
||||
is( scalar(@left), 0, '... and the capture file is removed' );
|
||||
}
|
||||
}
|
||||
|
||||
done_testing();
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Spec;
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
my $repo_root = File::Spec->rel2abs(File::Spec->catdir($FindBin::Bin, '..', '..'));
|
||||
|
||||
my $spec = read_file('xCAT-genesis-builder/xCAT-genesis-base.spec');
|
||||
like($spec, qr/^BuildRequires:\s+kernel-core$/m, 'genesis build installs the kernel core');
|
||||
like($spec, qr/^BuildRequires:\s+kernel-modules$/m, 'genesis build installs the standard kernel modules');
|
||||
like($spec, qr/^BuildRequires:\s+kernel-modules-extra$/m, 'genesis build installs the extra kernel modules');
|
||||
|
||||
my $dracut_module = read_file('xCAT-genesis-builder/dracut_105/el/module-setup.sh');
|
||||
like(
|
||||
$dracut_module,
|
||||
qr/installkernel\(\) \{.*?modules_dep=.*?while IFS= read -r modfile;.*?instmods "\$modname".*?done < "\$modules_dep"/s,
|
||||
'genesis dracut module installs every module available to the build'
|
||||
);
|
||||
|
||||
my $doxcat = read_file('xCAT-genesis-scripts/usr/bin/doxcat');
|
||||
like($doxcat, qr/^modprobe ib_ipoib$/m, 'genesis loads the IP over InfiniBand module');
|
||||
like(
|
||||
$doxcat,
|
||||
qr/if \[ -z "\$bootnic" \]; then\s+bootnic=`ip link show\|grep -B1 infiniband/s,
|
||||
'genesis checks InfiniBand addresses only after the Ethernet lookup misses'
|
||||
);
|
||||
|
||||
done_testing();
|
||||
|
||||
sub read_file {
|
||||
my ($file) = @_;
|
||||
my $path = File::Spec->catfile($repo_root, split m{/}, $file);
|
||||
open(my $fh, '<', $path) or die "open $path: $!";
|
||||
my $contents = do { local $/; <$fh> };
|
||||
close($fh) or die "close $path: $!";
|
||||
return $contents;
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Path qw(make_path);
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
my $go_xcat = "$FindBin::Bin/../../xCAT-server/share/xcat/tools/go-xcat";
|
||||
my $tmpdir = tempdir(CLEANUP => 1);
|
||||
my $driver = "$tmpdir/driver.sh";
|
||||
|
||||
open(my $driver_fh, '>', $driver) or die "open $driver: $!";
|
||||
print {$driver_fh} <<'DRIVER';
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
function_body=$(
|
||||
for function_name in \
|
||||
add_xcat_dep_common_repo_yum_or_zypper \
|
||||
xcat_dep_common_repo_configured \
|
||||
refresh_xcat_dep_repository_ids
|
||||
do
|
||||
awk -v name="$function_name" '
|
||||
$0 == "function " name "()" { copy = 1 }
|
||||
copy { print }
|
||||
copy && /^}$/ { exit }
|
||||
' "$GO_XCAT_SOURCE"
|
||||
done
|
||||
)
|
||||
eval "$function_body"
|
||||
|
||||
TMP_DIR=$TEST_TMP
|
||||
GO_XCAT_DEFAULT_BASE_URL=https://repo.example.invalid
|
||||
GO_XCAT_DEP_REPOSITORY_IDS=(xcat-dep)
|
||||
|
||||
yum() { :; }
|
||||
|
||||
download_file() {
|
||||
printf '%s\n' "$1" >>"$DOWNLOAD_LOG"
|
||||
[[ ${COMMON_PRESENT:-0} == 1 ]] || return 1
|
||||
: >"$2"
|
||||
}
|
||||
|
||||
add_repo_by_url_yum_or_zypper() {
|
||||
printf '%s %s\n' "$1" "$2" >>"$ADD_LOG"
|
||||
}
|
||||
|
||||
xcat_dep_common_repo_configured() { [[ -s "$ADD_LOG" ]]; }
|
||||
( add_xcat_dep_common_repo_yum_or_zypper "$@" )
|
||||
refresh_xcat_dep_repository_ids
|
||||
printf '%s\n' "${GO_XCAT_DEP_REPOSITORY_IDS[*]}" >"$ID_LOG"
|
||||
DRIVER
|
||||
close($driver_fh) or die "close $driver: $!";
|
||||
chmod(0755, $driver) or die "chmod $driver: $!";
|
||||
|
||||
sub run_case {
|
||||
my ($name, $present, @arguments) = @_;
|
||||
my $case_dir = "$tmpdir/$name";
|
||||
make_path($case_dir);
|
||||
local %ENV = (
|
||||
%ENV,
|
||||
ADD_LOG => "$case_dir/add.log",
|
||||
COMMON_PRESENT => $present,
|
||||
DOWNLOAD_LOG => "$case_dir/download.log",
|
||||
GO_XCAT_SOURCE => $go_xcat,
|
||||
ID_LOG => "$case_dir/id.log",
|
||||
TEST_TMP => $case_dir,
|
||||
);
|
||||
my $status = system('bash', $driver, @arguments);
|
||||
return ($status >> 8, $case_dir);
|
||||
}
|
||||
|
||||
sub read_file {
|
||||
my ($path) = @_;
|
||||
return '' unless -f $path;
|
||||
open(my $fh, '<', $path) or die "open $path: $!";
|
||||
my $content = do { local $/; <$fh> };
|
||||
close($fh) or die "close $path: $!";
|
||||
return $content;
|
||||
}
|
||||
|
||||
my ($status, $case_dir) = run_case('remote-present', 1, '', 'latest');
|
||||
is($status, 0, 'an available common repository is accepted');
|
||||
is(
|
||||
read_file("$case_dir/download.log"),
|
||||
"https://repo.example.invalid/yum/latest/xcat-dep/common/repodata/repomd.xml\n",
|
||||
'the default repository is probed before it is enabled',
|
||||
);
|
||||
is(
|
||||
read_file("$case_dir/add.log"),
|
||||
"https://repo.example.invalid/yum/latest/xcat-dep/common xcat-dep-common\n",
|
||||
'the common repository uses its own repository ID',
|
||||
);
|
||||
is(read_file("$case_dir/id.log"), "xcat-dep xcat-dep-common\n",
|
||||
'common packages are included in repository listings');
|
||||
|
||||
($status, $case_dir) = run_case('remote-missing', 0, '', '2.18');
|
||||
is($status, 0, 'a release without the common repository remains usable');
|
||||
is(read_file("$case_dir/add.log"), '', 'a missing common repository is not enabled');
|
||||
is(read_file("$case_dir/id.log"), "xcat-dep\n",
|
||||
'legacy package listings remain unchanged when common is absent');
|
||||
|
||||
($status, $case_dir) = run_case(
|
||||
'repo-file', 1, 'https://repo.example.invalid/custom/xcat-dep.repo', 'latest'
|
||||
);
|
||||
is($status, 0, 'a custom repository file remains supported');
|
||||
is(read_file("$case_dir/download.log"), '',
|
||||
'the common URL is not guessed from a custom repository file');
|
||||
is(read_file("$case_dir/add.log"), '',
|
||||
'a custom repository file does not enable an unrelated repository');
|
||||
|
||||
my $local_root = "$tmpdir/local-repository";
|
||||
make_path("$local_root/common/repodata");
|
||||
open(my $repomd_fh, '>', "$local_root/common/repodata/repomd.xml") or die $!;
|
||||
close($repomd_fh) or die $!;
|
||||
($status, $case_dir) = run_case('local-present', 0, $local_root, 'latest');
|
||||
is($status, 0, 'a local common repository is accepted');
|
||||
is(
|
||||
read_file("$case_dir/add.log"),
|
||||
"$local_root/common xcat-dep-common\n",
|
||||
'the local common repository is enabled beside the distribution repository',
|
||||
);
|
||||
|
||||
my $template_driver = "$tmpdir/template-driver.sh";
|
||||
open(my $template_fh, '>', $template_driver) or die "open $template_driver: $!";
|
||||
print {$template_fh} <<'DRIVER';
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
function_body=$(
|
||||
awk '
|
||||
/^function add_repo_by_url_yum_or_zypper\(\)/ { copy = 1 }
|
||||
copy { print }
|
||||
copy && /^}$/ { exit }
|
||||
' "$GO_XCAT_SOURCE"
|
||||
)
|
||||
eval "$function_body"
|
||||
|
||||
TMP_DIR=$TEST_TMP
|
||||
GO_XCAT_DEFAULT_INSTALL_PATH=/install/xcat
|
||||
yum() { :; }
|
||||
add_repo_by_file() { cp "$1" "$REPO_LOG"; }
|
||||
add_repo_by_url_yum_or_zypper \
|
||||
https://repo.example.invalid/xcat-dep/common xcat-dep-common optional
|
||||
DRIVER
|
||||
close($template_fh) or die "close $template_driver: $!";
|
||||
chmod(0755, $template_driver) or die "chmod $template_driver: $!";
|
||||
|
||||
my $template_log = "$tmpdir/generated-common.repo";
|
||||
local %ENV = (
|
||||
%ENV,
|
||||
GO_XCAT_SOURCE => $go_xcat,
|
||||
REPO_LOG => $template_log,
|
||||
TEST_TMP => $tmpdir,
|
||||
);
|
||||
$status = system('bash', $template_driver);
|
||||
is($status >> 8, 0, 'go-xcat can generate the optional common repository');
|
||||
like(read_file($template_log), qr/^skip_if_unavailable=1$/m,
|
||||
'the generated common repository stays optional during outages');
|
||||
like(read_file($template_log), qr/^repo_gpgcheck=1$/m,
|
||||
'the generated common repository verifies signed metadata');
|
||||
|
||||
done_testing();
|
||||
@@ -1,161 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# go-xcat checked for the EPEL and CRB repositories on EL9 only, and only when the version
|
||||
# carried a minor number, so CentOS Stream was never checked. On EL10 a management node without
|
||||
# them failed inside dnf install with a dependency error instead of the message that names the
|
||||
# missing repository, and the CRB message proposed a CentOS Stream repository file with
|
||||
# signature checks disabled. The probe used dnf list, which an installed copy of the probe
|
||||
# package satisfies, and which reports a failed query as a missing repository. The check
|
||||
# functions are taken from the shipped script and run against a dnf stand-in that answers for
|
||||
# each probe and records what was asked.
|
||||
|
||||
my $go_xcat = "$FindBin::Bin/../../xCAT-server/share/xcat/tools/go-xcat";
|
||||
plan skip_all => 'go-xcat not found' unless -r $go_xcat;
|
||||
|
||||
my $tmpdir = tempdir( CLEANUP => 1 );
|
||||
my $driver = "$tmpdir/driver.sh";
|
||||
my $calls = "$tmpdir/calls";
|
||||
open( my $fh, '>', $driver ) or die "open $driver: $!";
|
||||
print {$fh} <<'DRIVER';
|
||||
#!/bin/bash
|
||||
set -uo pipefail
|
||||
eval "$(awk '
|
||||
/^function (repo_carries|el9?_epel_and_crb_check|install_packages_(dnf|yum))\(\)/ { copy = 1 }
|
||||
copy { print }
|
||||
copy && /^}$/ { copy = 0 }
|
||||
' "$GO_XCAT_SOURCE")"
|
||||
EL9_EPEL_TEST_RPM="perl-Crypt-CBC"; EL_EPEL_TEST_RPM="perl-Crypt-CBC"
|
||||
EL9_CRB_TEST_RPM="perl-IO-Tty"; EL_CRB_TEST_RPM="perl-IO-Tty"
|
||||
# repoquery prints the name when an enabled repository carries the package and nothing
|
||||
# otherwise, and fails with a message when the repositories cannot be read. list -q exits 1
|
||||
# when neither a repository nor the installed set has the package.
|
||||
dnf() {
|
||||
echo "$*" >> "$CALLS"
|
||||
if [[ ${QUERY_FAIL:-0} == 1 ]]; then
|
||||
echo "Error: Failed to download metadata for repo 'epel'" >&2
|
||||
return 1
|
||||
fi
|
||||
[[ ${QUERY_WARNS:-0} == 1 ]] && echo "Warning: repository 'extras' metadata is stale" >&2
|
||||
local has=0
|
||||
case "$*" in
|
||||
*perl-Crypt-CBC*) has="$EPEL_HAS" ;;
|
||||
*perl-IO-Tty*) has="$CRB_HAS" ;;
|
||||
esac
|
||||
# A source repository answers for the name unless the query is limited to binary architectures.
|
||||
[[ ${SOURCE_ONLY:-0} == 1 && "$*" != *"--arch"* ]] && has=1
|
||||
case "$1" in
|
||||
repoquery) [[ $has == 1 ]] && { echo "${@: -1}"; echo "${@: -1}"; }; return 0 ;;
|
||||
list) [[ $has == 1 ]] && return 0; return 1 ;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
yum() { dnf "$@"; }
|
||||
# ENTRY=dnf or yum runs the installer function go-xcat dispatches to, which owns the check.
|
||||
case "${ENTRY:-check}" in
|
||||
dnf) install_packages_dnf -y xCAT ;;
|
||||
yum) install_packages_yum -y xCAT ;;
|
||||
*) if declare -F el_epel_and_crb_check >/dev/null; then el_epel_and_crb_check dnf; else el9_epel_and_crb_check dnf; fi ;;
|
||||
esac
|
||||
DRIVER
|
||||
close($fh);
|
||||
|
||||
# Runs the check as go-xcat would on a host of this distro and version. Returns the exit status,
|
||||
# the output, and the probes the dnf stand-in saw.
|
||||
sub check {
|
||||
my (%host) = @_;
|
||||
unlink $calls;
|
||||
local $ENV{GO_XCAT_SOURCE} = $go_xcat;
|
||||
local $ENV{CALLS} = $calls;
|
||||
local $ENV{GO_XCAT_LINUX_DISTRO} = $host{distro} || 'rocky';
|
||||
local $ENV{GO_XCAT_LINUX_VERSION} = $host{version};
|
||||
local $ENV{GO_XCAT_ARCH} = 'x86_64';
|
||||
local $ENV{EPEL_HAS} = $host{epel} ? 1 : 0;
|
||||
local $ENV{CRB_HAS} = $host{crb} ? 1 : 0;
|
||||
local $ENV{QUERY_FAIL} = $host{query_fail} ? 1 : 0;
|
||||
local $ENV{QUERY_WARNS} = $host{query_warns} ? 1 : 0;
|
||||
local $ENV{SOURCE_ONLY} = $host{source_only} ? 1 : 0;
|
||||
local $ENV{ENTRY} = $host{entry} || 'check';
|
||||
my $out = `bash '$driver' 2>&1`;
|
||||
my $rc = $? >> 8;
|
||||
my @probes;
|
||||
if ( open( my $cfh, '<', $calls ) ) { chomp( @probes = <$cfh> ); close($cfh); }
|
||||
return ( $rc, $out, join( ';', @probes ) );
|
||||
}
|
||||
|
||||
my ( $rc, $out, $probes );
|
||||
|
||||
( $rc, $out, $probes ) = check( version => '9.5', epel => 1, crb => 1 );
|
||||
is( $rc, 0, 'EL9 with EPEL and CRB passes' );
|
||||
like( $probes, qr/perl-Crypt-CBC.*;.*perl-IO-Tty/, '... after probing both repositories' );
|
||||
like( $probes, qr/^repoquery /, '... through repoquery, which an installed copy does not satisfy' );
|
||||
like( $probes, qr/--arch x86_64,noarch/, '... limited to the binary architectures' );
|
||||
|
||||
( $rc, $out, $probes ) = check( version => '9.5', epel => 0, crb => 1 );
|
||||
is( $rc, 1, 'EL9 without EPEL stops' );
|
||||
like( $out, qr/epel-release-latest-9\.noarch/, '... and names the EL9 EPEL release package' );
|
||||
|
||||
( $rc, $out, $probes ) = check( version => '10.2', epel => 1, crb => 1 );
|
||||
is( $rc, 0, 'EL10 with EPEL and CRB passes' );
|
||||
like( $probes, qr/perl-Crypt-CBC.*;.*perl-IO-Tty/, '... after probing both repositories' );
|
||||
|
||||
( $rc, $out, $probes ) = check( version => '10.2', epel => 0, crb => 1 );
|
||||
is( $rc, 1, 'EL10 without EPEL stops' );
|
||||
like( $out, qr/requires EPEL repository/, '... and names the missing repository' );
|
||||
like( $out, qr/epel-release-latest-10\.noarch/, '... and the EL10 EPEL release package' );
|
||||
|
||||
( $rc, $out, $probes ) = check( distro => 'rhel', version => '10.2', epel => 1, crb => 0 );
|
||||
is( $rc, 1, 'EL10 without CRB stops' );
|
||||
like( $out, qr/requires CRB repository/, '... and names the missing repository' );
|
||||
like( $out, qr/'dnf update epel-release' and then 'crb enable'/, '... with a current crb helper, which covers RHEL under RHSM and RHUI' );
|
||||
unlike( $out, qr/gpgcheck=0|centos-crb|subscription-manager/, '... and no repository file or subscription-only command' );
|
||||
|
||||
( $rc, $out, $probes ) = check( version => '10.2', epel => 0, crb => 1, source_only => 1 );
|
||||
is( $rc, 1, 'a source repository does not stand in for the binary one' );
|
||||
like( $out, qr/requires EPEL repository/, '... so the missing repository is still reported' );
|
||||
|
||||
( $rc, $out, $probes ) = check( distro => 'ol', version => '10.1', epel => 1, crb => 0 );
|
||||
is( $rc, 1, 'Oracle Linux 10 without CRB stops' );
|
||||
like( $out, qr/dnf config-manager --enable ol10_codeready_builder/, '... with the command that enables its CodeReady Builder' );
|
||||
|
||||
( $rc, $out, $probes ) = check( version => '10.2', epel => 1, crb => 1, query_fail => 1 );
|
||||
is( $rc, 1, 'a failed repository query stops' );
|
||||
like( $out, qr/Failed to download metadata/, '... with the package manager error' );
|
||||
unlike( $out, qr/requires EPEL repository/, '... and not as a missing repository' );
|
||||
|
||||
( $rc, $out, $probes ) = check( version => '10.2', epel => 0, crb => 1, query_warns => 1 );
|
||||
is( $rc, 1, 'a warning on stderr does not stand in for a package' );
|
||||
like( $out, qr/requires EPEL repository/, '... so the missing repository is still reported' );
|
||||
|
||||
( $rc, $out, $probes ) = check( version => '10.2', epel => 1, crb => 1, query_warns => 1 );
|
||||
is( $rc, 0, 'a warning beside a real match does not fail the check' );
|
||||
|
||||
( $rc, $out, $probes ) = check( distro => 'centos', version => '10', epel => 0, crb => 0 );
|
||||
is( $rc, 1, 'CentOS Stream 10, which reports the major version alone, is checked' );
|
||||
like( $out, qr/requires EPEL repository/, '... and told about EPEL' );
|
||||
|
||||
( $rc, $out, $probes ) = check( version => '8.10', epel => 0, crb => 0 );
|
||||
is( $rc, 0, 'EL8 is not checked' );
|
||||
is( $probes, '', '... and nothing is probed' );
|
||||
|
||||
( $rc, $out, $probes ) = check( distro => 'fedora', version => '42', epel => 0, crb => 0 );
|
||||
is( $rc, 0, 'Fedora is not checked' );
|
||||
|
||||
# The installer functions go-xcat dispatches to run the check before they install anything.
|
||||
foreach my $entry (qw(dnf yum)) {
|
||||
( $rc, $out, $probes ) = check( entry => $entry, version => '10.2', epel => 0, crb => 1 );
|
||||
is( $rc, 1, "install_packages_$entry on EL10 without EPEL stops" );
|
||||
like( $out, qr/requires EPEL repository/, '... with the EPEL message' );
|
||||
unlike( $probes, qr/install/, '... before anything is installed' );
|
||||
|
||||
( $rc, $out, $probes ) = check( entry => $entry, version => '10.2', epel => 1, crb => 1 );
|
||||
is( $rc, 0, "install_packages_$entry on EL10 with EPEL and CRB installs" );
|
||||
like( $probes, qr/perl-IO-Tty.*;.*install initscripts.*;.*install xCAT/, '... after the check passed' );
|
||||
}
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# go-xcat installs a fixed package list. On riscv64 that list asked for the amd64 and ppc64
|
||||
# Genesis scripts and bases, so the supported install path put the x86 Genesis on a riscv64
|
||||
# management node even though the xcat metapackage excludes it there.
|
||||
#
|
||||
# The x86 boot loaders are a different matter: they are payload the management node serves to x86
|
||||
# nodes, so they belong on a riscv64 management node of a mixed cluster and must survive the filter.
|
||||
#
|
||||
# The function that builds the riscv64 list is taken from the shipped script and run, so the
|
||||
# assertions read the package names go-xcat would hand to the package manager.
|
||||
|
||||
my $go_xcat = "$FindBin::Bin/../../xCAT-server/share/xcat/tools/go-xcat";
|
||||
plan skip_all => 'go-xcat not found' unless -r $go_xcat;
|
||||
|
||||
my $tmpdir = tempdir( CLEANUP => 1 );
|
||||
my $driver = "$tmpdir/driver.sh";
|
||||
open( my $fh, '>', $driver ) or die "open $driver: $!";
|
||||
print {$fh} <<'DRIVER';
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
eval "$(awk '
|
||||
$0 == "function riscv64_install_list()" { copy = 1 }
|
||||
copy { print }
|
||||
copy && /^}$/ { exit }
|
||||
' "$GO_XCAT_SOURCE")"
|
||||
|
||||
# The real list, as go-xcat defines it for each package manager.
|
||||
if [[ ${WITH_DPKG:-0} == 1 ]]; then
|
||||
dpkg() { :; }
|
||||
list=(perl-xcat xcat-client xcat xcat-buildkit
|
||||
xcat-genesis-scripts-amd64 xcat-genesis-scripts-ppc64 xcat-server
|
||||
elilo-xcat grub2-xcat ipmitool-xcat syslinux-xcat
|
||||
xcat-genesis-base-amd64 xcat-genesis-base-ppc64 xnba-undi)
|
||||
else
|
||||
type() { return 1; }
|
||||
list=(perl-xCAT xCAT-client xCAT xCAT-buildkit
|
||||
xCAT-genesis-scripts-ppc64 xCAT-genesis-scripts-x86_64 xCAT-server
|
||||
elilo-xcat grub2-xcat ipmitool-xcat syslinux-xcat
|
||||
xCAT-genesis-base-ppc64 xCAT-genesis-base-x86_64 xnba-undi)
|
||||
fi
|
||||
riscv64_install_list "${list[@]}"
|
||||
DRIVER
|
||||
close($fh);
|
||||
chmod 0755, $driver;
|
||||
|
||||
sub riscv64_list {
|
||||
my ($with_dpkg) = @_;
|
||||
my $out = `GO_XCAT_SOURCE='$go_xcat' WITH_DPKG=$with_dpkg bash '$driver' 2>&1`;
|
||||
is( $?, 0, "the riscv64 list builds (dpkg=$with_dpkg)" ) or diag($out);
|
||||
return [ grep { length } split( /\n/, $out ) ];
|
||||
}
|
||||
|
||||
foreach my $case ( [ 1, 'deb', 'xcat-genesis-openembedded-riscv64', 'xcat-server' ],
|
||||
[ 0, 'rpm', 'xCAT-genesis-openembedded-riscv64', 'xCAT-server' ] )
|
||||
{
|
||||
my ( $with_dpkg, $name, $genesis, $server ) = @{$case};
|
||||
my $list = riscv64_list($with_dpkg);
|
||||
|
||||
is_deeply( [ grep { /genesis-scripts-/i } @{$list} ], [],
|
||||
"$name: no legacy Genesis scripts on riscv64" );
|
||||
is_deeply( [ grep { /genesis-base-/i } @{$list} ], [],
|
||||
"$name: no legacy Genesis base on riscv64" );
|
||||
is_deeply( [ sort grep { /^(elilo-xcat|syslinux-xcat|xnba-undi)$/ } @{$list} ],
|
||||
[ sort qw(elilo-xcat syslinux-xcat xnba-undi) ],
|
||||
"$name: the x86 boot payload a mixed cluster needs is still installed" );
|
||||
|
||||
ok( scalar( grep { $_ eq $genesis } @{$list} ),
|
||||
"$name: the riscv64 OpenEmbedded Genesis package is asked for" );
|
||||
|
||||
# The filter must take nothing else with it.
|
||||
foreach my $keep ( $server, 'grub2-xcat', 'ipmitool-xcat' ) {
|
||||
ok( scalar( grep { $_ eq $keep } @{$list} ), "$name: $keep is still installed" );
|
||||
}
|
||||
}
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# grub2 reads its configuration as a script. A word carrying a command separator ends the
|
||||
# linux command, so the kernel never sees the rest of the line. The Ubuntu installer seed
|
||||
# (ds=nocloud-net;s=<url>) is written as one such word: the node booted without the seed URL
|
||||
# and without BOOTIF, and the installer waited for someone to answer its questions.
|
||||
|
||||
use lib "$FindBin::Bin/../../perl-xCAT";
|
||||
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
|
||||
my $plugin = "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/grub2.pm";
|
||||
plan skip_all => 'grub2.pm not found' unless -r $plugin;
|
||||
eval { require $plugin; 1 } or plan skip_all => "could not load grub2.pm: $@";
|
||||
|
||||
my $quote = \&xCAT_plugin::grub2::quote_kcmdline;
|
||||
|
||||
my $seed = 'imgurl=http://mn/rootimg.cpio.gz ds=nocloud-net;s=http://mn/install/autoinst/n1/ quiet';
|
||||
is( $quote->($seed),
|
||||
'imgurl=http://mn/rootimg.cpio.gz ds=nocloud-net\;s=http://mn/install/autoinst/n1/ quiet',
|
||||
'the installer seed keeps the arguments after it' );
|
||||
|
||||
my $plain = 'imgurl=http://mn/rootimg.cpio.gz XCAT=10.0.0.1:3001 console=ttyS0,115200 quiet';
|
||||
is( $quote->($plain), $plain, 'a command line without a separator is unchanged' );
|
||||
|
||||
is( $quote->(undef), undef, 'an undefined command line stays undefined' );
|
||||
is( $quote->(''), '', 'an empty command line stays empty' );
|
||||
|
||||
for my $char ( ';', '{', '}', '|', '&', '<', '>', '(', ')' ) {
|
||||
is( $quote->("first opt=a${char}b last"), "first opt=a\\${char}b last",
|
||||
"a separator $char is escaped" );
|
||||
}
|
||||
|
||||
# grub2 removes the quoting before the kernel sees the value, so a value the caller quoted
|
||||
# must keep the quotes it was given rather than gaining a second layer.
|
||||
is( $quote->('first opt="a;b c" last'), 'first opt="a;b c" last',
|
||||
'a double quoted value is passed through unchanged' );
|
||||
is( $quote->(q{first 'ds=nocloud-net;s=http://mn/seed' last}),
|
||||
q{first 'ds=nocloud-net;s=http://mn/seed' last},
|
||||
'a single quoted value is passed through unchanged' );
|
||||
|
||||
# A quoted span and a bare separator in the same word: the span keeps its meaning and only
|
||||
# the separator outside it is escaped.
|
||||
is( $quote->(q{opt='a b';c}), q{opt='a b'\;c},
|
||||
'a separator beside a quoted span is escaped without touching the span' );
|
||||
is( $quote->('opt="a;b c" other=x;y'), 'opt="a;b c" other=x\;y',
|
||||
'a bare separator beside a quoted word is escaped' );
|
||||
|
||||
# An escaped separator is already literal to grub2, so escaping it again would hand the
|
||||
# kernel a backslash the caller never wrote.
|
||||
is( $quote->('first ds=nocloud-net\;s=http://mn/seed last'),
|
||||
'first ds=nocloud-net\;s=http://mn/seed last',
|
||||
'a separator the caller escaped is passed through unchanged' );
|
||||
is( $quote->('a\;b c;d'), 'a\;b c\;d',
|
||||
'an escaped separator does not stop a real one being escaped' );
|
||||
|
||||
is( $quote->('BOOTIF=$net_default_mac x;y'), 'BOOTIF=$net_default_mac x\;y',
|
||||
'a variable reference is left alone so BOOTIF still expands' );
|
||||
|
||||
done_testing();
|
||||
@@ -2,11 +2,23 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Path qw(make_path);
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use lib "$FindBin::Bin/../../perl-xCAT";
|
||||
use Test::More;
|
||||
|
||||
# xCAT modules put $::XCATROOT/lib/perl ahead of @INC as they compile, so on a host with xCAT
|
||||
# installed the schema, loaded after them, would come from /opt/xcat. XCATROOT points at this
|
||||
# checkout before any of them compiles.
|
||||
BEGIN {
|
||||
my $root = tempdir( CLEANUP => 1 );
|
||||
make_path("$root/lib");
|
||||
symlink( "$FindBin::Bin/../../perl-xCAT", "$root/lib/perl" ) or die "symlink: $!";
|
||||
$ENV{XCATROOT} = $root;
|
||||
}
|
||||
|
||||
use XCAT::Test::File qw(repo_path);
|
||||
use xCAT::ProfiledNodeUtils;
|
||||
use xCAT::Utils;
|
||||
@@ -56,9 +68,13 @@ is( xCAT::ProfiledNodeUtils::cal_netboot( $rule_table, [ 'aarch64', 'rhels', '9'
|
||||
# ---------------------------------------------------------------------------
|
||||
# Schema descriptions
|
||||
# ---------------------------------------------------------------------------
|
||||
SKIP: {
|
||||
skip 'xCAT::Schema is not loadable here', 5
|
||||
unless eval { require lib; lib->import( repo_path('perl-xCAT') ); require xCAT::Schema; 1 };
|
||||
# The schema comes from this checkout, not from an installed xCAT, or the descriptions checked
|
||||
# below would be those of whatever version the host carries.
|
||||
{
|
||||
require xCAT::Schema;
|
||||
require Cwd;
|
||||
like( Cwd::realpath( $INC{'xCAT/Schema.pm'} ), qr/^\Q@{[ Cwd::realpath( repo_path('perl-xCAT') ) ]}\E/,
|
||||
'xCAT::Schema is loaded from the checkout' );
|
||||
|
||||
like( $xCAT::Schema::tabspec{nodetype}{descriptions}{arch}, qr/\briscv64\b/,
|
||||
'nodetype.arch documents riscv64 as a valid value' );
|
||||
@@ -68,8 +84,9 @@ SKIP: {
|
||||
'osimage.osarch documents riscv64 as a valid value' );
|
||||
like( $xCAT::Schema::tabspec{osimage}{descriptions}{osarch}, qr/\bs390x\b/,
|
||||
'osimage.osarch documents s390x as a valid value' );
|
||||
like( $xCAT::Schema::tabspec{noderes}{descriptions}{netboot}, qr/riscv64\s+>=el10\s+grub2,grub2-http,grub2-tftp/,
|
||||
'noderes.netboot documents the riscv64 grub2 methods' );
|
||||
like( $xCAT::Schema::tabspec{noderes}{descriptions}{netboot},
|
||||
qr/riscv64\s+>=el10, >=ubuntu24\.04\s+grub2,grub2-http,grub2-tftp/,
|
||||
'noderes.netboot documents the riscv64 grub2 methods for EL and Ubuntu' );
|
||||
}
|
||||
|
||||
done_testing();
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use File::Spec;
|
||||
use Test::More;
|
||||
|
||||
my $repo_root = File::Spec->catdir( $FindBin::Bin, '..', '..' );
|
||||
my %scripts = (
|
||||
'legacy install post.xcat' => File::Spec->catfile(
|
||||
$repo_root,
|
||||
'xCAT-server/share/xcat/install/scripts/post.xcat'
|
||||
),
|
||||
'legacy netboot xcatdsklspost' => File::Spec->catfile(
|
||||
$repo_root,
|
||||
'xCAT/postscripts/xcatdsklspost'
|
||||
),
|
||||
);
|
||||
|
||||
foreach my $path ( values %scripts ) {
|
||||
plan skip_all => "$path not found" unless -r $path;
|
||||
}
|
||||
|
||||
foreach my $name ( sort keys %scripts ) {
|
||||
my $path = $scripts{$name};
|
||||
open( my $fh, '<', $path ) or die "Unable to read $path: $!";
|
||||
my $script = do { local $/; <$fh> };
|
||||
close($fh);
|
||||
|
||||
like(
|
||||
$script,
|
||||
qr/--reject\s+"index\.html\*,post\.xcat\.ng,post\.xcat\.rhels10"/,
|
||||
"$name recursive wget ignores dispatcher scripts that contain literal HTML-link regexes"
|
||||
);
|
||||
like(
|
||||
$script,
|
||||
qr/Newer\s+(?:#\s+)?wget\s+parses\s+HTML-looking\s+regex\s+strings/s,
|
||||
"$name download policy documents why dispatcher scripts are excluded"
|
||||
);
|
||||
unlike(
|
||||
$script,
|
||||
qr/<a\s+href=/i,
|
||||
"$name comments do not contain HTML links that wget can recurse into"
|
||||
);
|
||||
}
|
||||
|
||||
done_testing();
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Spec;
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
my $script = File::Spec->catfile( $FindBin::Bin, '..', '..',
|
||||
'xCAT', 'postscripts', 'remoteshell' );
|
||||
plan skip_all => 'remoteshell not found' unless -r $script;
|
||||
|
||||
open( my $fh, '<', $script ) or die "Unable to read $script: $!";
|
||||
my $source = do { local $/; <$fh> };
|
||||
close($fh);
|
||||
|
||||
# A kill without the hyphen reads the signal number as one more process id, so
|
||||
# the target gets the default signal, which a process can catch, and the
|
||||
# process with that id is signalled as well.
|
||||
my @bare = ( $source =~ /^[^#\n]*\bkill\s+\d/gm );
|
||||
is( scalar(@bare), 0, 'no kill gives the signal number without a hyphen' );
|
||||
|
||||
like( $source, qr/kill -9 \$PIDLIST/,
|
||||
'the ssh daemon gets the signal that a process cannot catch' );
|
||||
|
||||
done_testing();
|
||||
@@ -1,62 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Spec;
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
my $script = File::Spec->catfile( $FindBin::Bin, '..', '..',
|
||||
'xCAT', 'postscripts', 'remoteshell' );
|
||||
plan skip_all => 'remoteshell not found' unless -r $script;
|
||||
plan skip_all => 'no bash' unless -x '/bin/bash';
|
||||
|
||||
open( my $fh, '<', $script ) or die "Unable to read $script: $!";
|
||||
my $source = do { local $/; <$fh> };
|
||||
close($fh);
|
||||
|
||||
my ($loop) = $source =~ /(waited=0\n.*?\n done)/s;
|
||||
ok( defined($loop), 'the wait loop was found in the postscript' );
|
||||
|
||||
# Run the loop of the postscript against real processes, with a shorter bound
|
||||
# so that the test does not spend the whole timeout of the postscript.
|
||||
sub waits_for {
|
||||
my (@pids) = @_;
|
||||
( my $body = $loop ) =~ s/\$waited -lt 10/\$waited -lt 3/;
|
||||
my $list = join( ' ', @pids );
|
||||
my $out = `/bin/bash -c 'PIDLIST="$list"\n$body\necho \$alive' 2>/dev/null`;
|
||||
chomp $out;
|
||||
return $out;
|
||||
}
|
||||
|
||||
# A process that is still running must be reported as alive, and the loop must
|
||||
# give up rather than run for ever.
|
||||
my $child = fork();
|
||||
if ( !defined $child ) { plan skip_all => 'cannot fork' }
|
||||
if ( $child == 0 ) { exec( 'sleep', '30' ); exit 1 }
|
||||
my $start = time;
|
||||
is( waits_for($child), '1', 'a process that is still running is reported alive' );
|
||||
cmp_ok( time - $start, '<', 10, 'the loop gives up instead of waiting for ever' );
|
||||
kill 'KILL', $child;
|
||||
waitpid( $child, 0 );
|
||||
|
||||
# A process that has ended must be reported as gone, without waiting.
|
||||
my $gone = fork();
|
||||
if ( $gone == 0 ) { exit 0 }
|
||||
waitpid( $gone, 0 );
|
||||
$start = time;
|
||||
is( waits_for($gone), '0', 'a process that has ended is reported gone' );
|
||||
cmp_ok( time - $start, '<', 3, 'no time is spent once the process is gone' );
|
||||
|
||||
# The bound of the postscript itself, and the shape of the test.
|
||||
like( $source, qr/while \[ \$waited -lt 10 \]/, 'the postscript waits at most ten seconds' );
|
||||
like( $source, qr/kill -0 \$pid/, 'the check for a running process sends no signal' );
|
||||
like( $source, qr/kill -9 \$PIDLIST/, 'the daemon still gets the signal it cannot catch' );
|
||||
|
||||
# The wait has to happen before the new daemon starts.
|
||||
my ($block) = $source =~ /(PIDLIST=.*?\/usr\/sbin\/sshd)/s;
|
||||
ok( defined($block), 'the fallback block was found' );
|
||||
cmp_ok( index( $block, 'waited=0' ), '<', index( $block, '/usr/sbin/sshd' ),
|
||||
'the wait comes before the new daemon starts' );
|
||||
|
||||
done_testing();
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
use Test::More;
|
||||
|
||||
my $pre_sles_path = defined $ENV{XCATROOT} ? "$ENV{XCATROOT}/share/xcat/install/scripts/pre.sles" : '';
|
||||
$pre_sles_path = "xCAT-server/share/xcat/install/scripts/pre.sles"
|
||||
unless -f $pre_sles_path;
|
||||
|
||||
plan skip_all => "pre.sles not found" unless -f $pre_sles_path;
|
||||
|
||||
my $src = do { local $/; open my $fh, '<', $pre_sles_path or die $!; <$fh> };
|
||||
|
||||
like($src, qr/sub set_sles11_uefi_bootloader\b|set_sles11_uefi_bootloader\(\)/, 'SLES UEFI bootloader helper exists');
|
||||
like($src, qr/install=\.\*sles11/, 'SLES 11 UEFI bootloader change is scoped to SLES 11 install media');
|
||||
like($src, qr/<loader_type>elilo<\/loader_type>/, 'SLES 11 UEFI install selects elilo');
|
||||
like($src, qr/<location>mbr<\/location>/, 'legacy MBR template value is replaced at install time');
|
||||
like($src, qr/if \[ -d \/sys\/firmware\/efi \]; then\s+sed .*?set_sles11_uefi_bootloader/s, 'UEFI default partitioning applies bootloader helper');
|
||||
|
||||
done_testing();
|
||||
@@ -1,17 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
use Test::More;
|
||||
|
||||
my $script_path = defined $ENV{XCATROOT} ? "$ENV{XCATROOT}/share/xcat/netboot/add-on/statelite/add_ssh" : '';
|
||||
$script_path = "xCAT-server/share/xcat/netboot/add-on/statelite/add_ssh"
|
||||
unless -f $script_path;
|
||||
|
||||
plan skip_all => "add_ssh not found" unless -f $script_path;
|
||||
|
||||
my $script = do { local $/; open my $fh, '<', $script_path or die $!; <$fh> };
|
||||
|
||||
like($script, qr/MaxStartups 100:30:200/, 'add_ssh caps MaxStartups below systemd soft nofile limit');
|
||||
unlike($script, qr/echo\s+"MaxStartups 1024"/, 'add_ssh does not force MaxStartups 1024');
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,256 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Path qw(make_path);
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# riscv64 nodes have no boot loader unless one reaches /tftpboot/boot/grub2. The image on
|
||||
# the media cannot be used: it carries a built-in configuration that looks for the live
|
||||
# filesystem, so a node that loads it drops to a grub prompt instead of reading the network
|
||||
# configuration. copycd builds a netboot image from the grub2 package the media ship.
|
||||
#
|
||||
# dpkg-deb and grub-mkimage are shadowed by stubs ahead of $PATH, because a management node
|
||||
# is the only place they exist. They record what copycd asked for, so the arguments that
|
||||
# decide whether the image can boot over the network are what the assertions read.
|
||||
|
||||
BEGIN {
|
||||
package xCAT::TableUtils;
|
||||
our $tftpdir;
|
||||
sub getTftpDir { return $tftpdir; }
|
||||
$INC{'xCAT/TableUtils.pm'} = __FILE__;
|
||||
}
|
||||
|
||||
use lib "$FindBin::Bin/../../perl-xCAT";
|
||||
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
|
||||
my $plugin = "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/debian.pm";
|
||||
plan skip_all => 'debian.pm not found' unless -r $plugin;
|
||||
eval { require $plugin; 1 } or plan skip_all => "could not load debian.pm: $@";
|
||||
|
||||
my $stubs = tempdir(CLEANUP => 1);
|
||||
my $log = "$stubs/mkimage.args";
|
||||
|
||||
sub write_stub {
|
||||
my ($name, $body) = @_;
|
||||
open(my $fh, '>', "$stubs/$name") or die $!;
|
||||
print {$fh} "#!/bin/bash\n$body";
|
||||
close($fh);
|
||||
chmod 0755, "$stubs/$name";
|
||||
}
|
||||
|
||||
# dpkg-deb -x <package> <dir> lays down the module tree the package carries.
|
||||
write_stub('dpkg-deb', <<'SH');
|
||||
dir="${!#}"
|
||||
mkdir -p "$dir/usr/lib/grub/riscv64-efi"
|
||||
: > "$dir/usr/lib/grub/riscv64-efi/kernel.img"
|
||||
SH
|
||||
|
||||
# grub-mkimage records its arguments and writes a riscv64 PE image to the -o path. Like the real
|
||||
# one it embeds the prefix and the name of every module it was asked for, which is what tells a
|
||||
# grub2 image apart from a file that merely carries the headers.
|
||||
write_stub('grub-mkimage', <<'SH');
|
||||
echo "$@" >> "$MKIMAGE_LOG"
|
||||
[ -n "$MKIMAGE_FAIL" ] && exit 1
|
||||
out=""; prefix=""; modules=()
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-o) out="$2"; shift 2 ;;
|
||||
-p) prefix="$2"; shift 2 ;;
|
||||
-O|-d) shift 2 ;;
|
||||
*) modules+=("$1"); shift ;;
|
||||
esac
|
||||
done
|
||||
perl -e 'my ($prefix, @modules) = @ARGV;
|
||||
my $i = "MZ" . "\0" x 58 . pack("V", 64) . "PE\0\0" . pack("v", 0x5064)
|
||||
. pack("v", 1) . "\0" x 16 . pack("v", 0x20b) . "\0" x 14 . pack("V", 0x1000)
|
||||
. "\0" x 36 . pack("V", 4096) . pack("V", 4096) . "\0" x 4 . pack("v", 10) . "\0" x 2;
|
||||
$i .= "$prefix\0";
|
||||
$i .= "\0$_\0" for @modules;
|
||||
print $i, "\0" x (4096 - length $i)' "$prefix" "${modules[@]}" > "$out"
|
||||
SH
|
||||
|
||||
$ENV{PATH} = "$stubs:$ENV{PATH}";
|
||||
$ENV{MKIMAGE_LOG} = $log;
|
||||
|
||||
sub media_with {
|
||||
my (@files) = @_;
|
||||
my $root = tempdir(CLEANUP => 1);
|
||||
foreach my $file (@files) {
|
||||
my $full = "$root/$file";
|
||||
($full =~ m{^(.*)/[^/]+$}) and make_path($1);
|
||||
open(my $fh, '>', $full) or die $!;
|
||||
print {$fh} "content of $file";
|
||||
close($fh);
|
||||
}
|
||||
return $root;
|
||||
}
|
||||
|
||||
sub publish {
|
||||
my ($arch, $media) = @_;
|
||||
$xCAT::TableUtils::tftpdir = tempdir(CLEANUP => 1);
|
||||
unlink $log;
|
||||
my @said;
|
||||
xCAT_plugin::debian::install_media_grub2_loader(
|
||||
$media, $arch,
|
||||
sub { push @said, ($_[0]->{data} // ()), @{ $_[0]->{warning} || [] } });
|
||||
my $target = "$xCAT::TableUtils::tftpdir/boot/grub2/grub2.$arch";
|
||||
return {
|
||||
published => (-e $target ? 1 : 0),
|
||||
built => (-r $log ? do { open my $fh, '<', $log; local $/; <$fh> } : ''),
|
||||
said => join(' ', @said),
|
||||
};
|
||||
}
|
||||
|
||||
my $package = 'pool/main/g/grub2/grub-efi-riscv64-bin_2.12-1ubuntu7.3_riscv64.deb';
|
||||
|
||||
my $riscv = publish('riscv64', media_with($package, 'casper/vmlinux'));
|
||||
is($riscv->{published}, 1, 'riscv64 media publish a grub2 loader');
|
||||
like($riscv->{built}, qr/-O riscv64-efi/, 'the image is built for the riscv64 firmware');
|
||||
like($riscv->{built}, qr{-p /boot/grub2},
|
||||
'the image looks for its configuration where nodeset writes it');
|
||||
like($riscv->{built}, qr{-d \S+/usr/lib/grub/riscv64-efi},
|
||||
'the modules come from the package the media ship');
|
||||
like($riscv->{built}, qr/\befinet\b.*\btftp\b/s, 'the image can reach the network');
|
||||
like($riscv->{built}, qr/\bhttp\b/, 'the image can read a configuration over HTTP');
|
||||
like($riscv->{said}, qr/Installed .*grub2\.riscv64 from the media/,
|
||||
'copycd says where the loader came from');
|
||||
|
||||
my $x86 = publish('x86_64', media_with('EFI/boot/bootx64.efi', 'casper/vmlinuz'));
|
||||
is($x86->{published}, 0, 'media of another architecture publish nothing');
|
||||
|
||||
my $none = publish('riscv64', media_with('casper/vmlinux'));
|
||||
is($none->{published}, 0, 'riscv64 media without the grub2 package publish nothing');
|
||||
like($none->{said}, qr/No grub2\.riscv64 boot loader was installed/,
|
||||
'copycd says a riscv64 node will not boot without a loader');
|
||||
|
||||
# a build that fails must leave no loader behind, so nodeset reports the missing file
|
||||
$ENV{MKIMAGE_FAIL} = 1;
|
||||
my $failed = publish('riscv64', media_with($package));
|
||||
delete $ENV{MKIMAGE_FAIL};
|
||||
is($failed->{published}, 0, 'a failed build leaves no loader');
|
||||
like($failed->{said}, qr/No grub2\.riscv64 boot loader was installed/,
|
||||
'copycd reports a build that failed');
|
||||
|
||||
# The image is built beside the target and renamed, so an interrupted run cannot leave a
|
||||
# partial loader behind for nodeset to hand out.
|
||||
$xCAT::TableUtils::tftpdir = tempdir(CLEANUP => 1);
|
||||
make_path("$xCAT::TableUtils::tftpdir/boot/grub2");
|
||||
$ENV{MKIMAGE_FAIL} = 1;
|
||||
xCAT_plugin::debian::install_media_grub2_loader(media_with($package), 'riscv64', undef);
|
||||
delete $ENV{MKIMAGE_FAIL};
|
||||
my @leftovers = glob("$xCAT::TableUtils::tftpdir/boot/grub2/grub2.riscv64*");
|
||||
is(scalar @leftovers, 0, 'a failed build leaves nothing beside the target either');
|
||||
|
||||
# An empty target is what an interrupted older run left behind, so it must not be mistaken
|
||||
# for an installed loader.
|
||||
$xCAT::TableUtils::tftpdir = tempdir(CLEANUP => 1);
|
||||
make_path("$xCAT::TableUtils::tftpdir/boot/grub2");
|
||||
open(my $efh, '>', "$xCAT::TableUtils::tftpdir/boot/grub2/grub2.riscv64") or die $!;
|
||||
close($efh);
|
||||
unlink $log;
|
||||
xCAT_plugin::debian::install_media_grub2_loader(media_with($package), 'riscv64', undef);
|
||||
ok(-s "$xCAT::TableUtils::tftpdir/boot/grub2/grub2.riscv64",
|
||||
'an empty loader left by an earlier run is replaced');
|
||||
|
||||
# UEFI loads the loader as a PE image for one machine. A real riscv64 one is kept, and
|
||||
# anything else -- text, a header-only stub, another architecture's loader -- is replaced,
|
||||
# because a node given one of those cannot boot.
|
||||
sub uefi_image {
|
||||
my (%f) = @_;
|
||||
my $size = $f{size} // 4096;
|
||||
my $image =
|
||||
"MZ" . ( "\0" x 58 ) . pack( 'V', 64 )
|
||||
. "PE\0\0"
|
||||
. pack( 'v', $f{machine} // 0x5064 )
|
||||
. pack( 'v', $f{sections} // 1 ) . ( "\0" x 16 )
|
||||
. pack( 'v', 0x20b ) . ( "\0" x 14 )
|
||||
. pack( 'V', $f{entry} // 0x1000 ) . ( "\0" x 36 )
|
||||
. pack( 'V', $size ) . pack( 'V', $size ) . ( "\0" x 4 )
|
||||
. pack( 'v', $f{subsystem} // 10 ) . ( "\0" x 2 );
|
||||
my $prefix = exists $f{prefix} ? $f{prefix} : '/boot/grub2';
|
||||
$image .= "$prefix\0" if length $prefix;
|
||||
# grub-mkimage records the name of every embedded module, so a real loader carries them.
|
||||
my @modules = exists $f{modules}
|
||||
? @{ $f{modules} }
|
||||
: qw(efinet tftp http linux normal configfile search);
|
||||
$image .= "\0$_\0" for @modules;
|
||||
return $image . ( "\0" x ( $size - length $image ) );
|
||||
}
|
||||
|
||||
sub existing_loader_kept {
|
||||
my ($bytes) = @_;
|
||||
$xCAT::TableUtils::tftpdir = tempdir( CLEANUP => 1 );
|
||||
make_path("$xCAT::TableUtils::tftpdir/boot/grub2");
|
||||
my $target = "$xCAT::TableUtils::tftpdir/boot/grub2/grub2.riscv64";
|
||||
open( my $fh, '>', $target ) or die $!;
|
||||
binmode($fh);
|
||||
print {$fh} $bytes;
|
||||
close($fh);
|
||||
xCAT_plugin::debian::install_media_grub2_loader( media_with($package), 'riscv64', undef );
|
||||
open( my $rfh, '<', $target ) or die $!;
|
||||
binmode($rfh);
|
||||
my $now = do { local $/; <$rfh> };
|
||||
close($rfh);
|
||||
return $now eq $bytes;
|
||||
}
|
||||
|
||||
ok( existing_loader_kept( uefi_image() ), 'a riscv64 loader already in place is kept' );
|
||||
|
||||
ok( !existing_loader_kept('not a loader at all'),
|
||||
'a file that is not a UEFI image is replaced' );
|
||||
ok( !existing_loader_kept('MZ and nothing else'),
|
||||
'a file that only starts with the DOS signature is replaced' );
|
||||
ok( !existing_loader_kept( substr( uefi_image(), 0, 200 ) ),
|
||||
'a loader truncated to its headers is replaced' );
|
||||
ok( !existing_loader_kept( uefi_image( machine => 0x8664 ) ),
|
||||
"another architecture's loader is replaced" );
|
||||
|
||||
# The fields below are what makes a PE image executable at all. A file carrying the
|
||||
# signatures but none of them cannot boot a node, so it must not be mistaken for a loader.
|
||||
ok( !existing_loader_kept( uefi_image( sections => 0 ) ),
|
||||
'an image with no sections to load is replaced' );
|
||||
ok( !existing_loader_kept( uefi_image( entry => 0 ) ),
|
||||
'an image with no entry point is replaced' );
|
||||
ok( !existing_loader_kept( uefi_image( subsystem => 3 ) ),
|
||||
'an image that is not an EFI application is replaced' );
|
||||
|
||||
# The image on the media carries the same modules but not the prefix this plugin builds with,
|
||||
# so it looks for the live filesystem instead of the configuration nodeset writes.
|
||||
ok( !existing_loader_kept( uefi_image( prefix => '' ) ),
|
||||
'a valid EFI image built for another boot path is replaced' );
|
||||
|
||||
# Headers and a prefix are cheap to fabricate and say nothing about what the image can do.
|
||||
# Without the modules a net boot goes through, the file cannot fetch a kernel over the network.
|
||||
ok( !existing_loader_kept( uefi_image( modules => [] ) ),
|
||||
'an image carrying no grub2 modules is replaced' );
|
||||
ok( !existing_loader_kept( uefi_image( modules => [qw(linux normal configfile search)] ) ),
|
||||
'an image with no network modules is replaced' );
|
||||
ok( existing_loader_kept( uefi_image( modules => [qw(efinet tftp http linux normal configfile search gzio)] ) ),
|
||||
'an image carrying more modules than the minimum is kept' );
|
||||
|
||||
# A rebuild that cannot run must leave the loader alone. The check cannot tell an image this
|
||||
# plugin did not build from one built for another boot path -- the media loader carries the same
|
||||
# modules -- so removing one on that evidence can take a working loader away from every node.
|
||||
$xCAT::TableUtils::tftpdir = tempdir( CLEANUP => 1 );
|
||||
make_path("$xCAT::TableUtils::tftpdir/boot/grub2");
|
||||
my $rejected = "$xCAT::TableUtils::tftpdir/boot/grub2/grub2.riscv64";
|
||||
open( my $bad, '>', $rejected ) or die $!;
|
||||
print {$bad} 'not a loader at all';
|
||||
close($bad);
|
||||
my @told;
|
||||
xCAT_plugin::debian::install_media_grub2_loader(
|
||||
media_with('casper/vmlinux'), 'riscv64',
|
||||
sub { push @told, ( $_[0]->{data} // () ), @{ $_[0]->{warning} || [] } } );
|
||||
ok( -e $rejected, 'a rejected loader survives a media that cannot replace it' );
|
||||
my $left = -e $rejected
|
||||
? do { open my $fh, '<', $rejected or die $!; local $/; <$fh> }
|
||||
: '(removed)';
|
||||
is( $left, 'not a loader at all', '... byte for byte' );
|
||||
like( join( ' ', @told ), qr/No grub2\.riscv64 boot loader was installed/,
|
||||
'and copycd says no loader was installed' );
|
||||
like( join( ' ', @told ), qr/was left alone/,
|
||||
'... and that it did not touch what was there' );
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# The stubbed test proves the decisions copycd makes. This one proves the artifact: it runs the
|
||||
# real grub-mkimage over the grub2 package of real Ubuntu media and checks that what comes out is
|
||||
# what the management node will serve to a riscv64 node.
|
||||
#
|
||||
# It needs copied riscv64 media and the grub2 build tools, so it runs where those exist:
|
||||
# XCAT_TEST_UBUNTU_RISCV64_MEDIA=/install/ubuntu24.04.4/riscv64 perl <this test>
|
||||
# Without them it skips, which is why the stubbed test still covers the decisions.
|
||||
|
||||
BEGIN {
|
||||
package xCAT::TableUtils;
|
||||
our $tftpdir;
|
||||
sub getTftpDir { return $tftpdir; }
|
||||
$INC{'xCAT/TableUtils.pm'} = __FILE__;
|
||||
}
|
||||
|
||||
my $media = $ENV{XCAT_TEST_UBUNTU_RISCV64_MEDIA};
|
||||
plan skip_all => 'set XCAT_TEST_UBUNTU_RISCV64_MEDIA to copied riscv64 media' unless $media;
|
||||
plan skip_all => "no media at $media" unless -d $media;
|
||||
foreach my $tool (qw(dpkg-deb grub-mkimage)) {
|
||||
my $found = grep { -x "$_/$tool" } split( /:/, $ENV{PATH} // '' );
|
||||
plan skip_all => "$tool is not installed" unless $found;
|
||||
}
|
||||
|
||||
use lib "$FindBin::Bin/../../perl-xCAT";
|
||||
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
|
||||
my $plugin = "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/debian.pm";
|
||||
plan skip_all => 'debian.pm not found' unless -r $plugin;
|
||||
eval { require $plugin; 1 } or plan skip_all => "could not load debian.pm: $@";
|
||||
|
||||
$xCAT::TableUtils::tftpdir = tempdir( CLEANUP => 1 );
|
||||
my @told;
|
||||
my $target = xCAT_plugin::debian::install_media_grub2_loader(
|
||||
$media, 'riscv64',
|
||||
sub { push @told, ( $_[0]->{data} // () ), @{ $_[0]->{warning} || [] } } );
|
||||
|
||||
ok( defined $target, 'the media produced a loader' )
|
||||
or diag( join( "\n", @told ) ), done_testing(), exit;
|
||||
is( $target, "$xCAT::TableUtils::tftpdir/boot/grub2/grub2.riscv64",
|
||||
'it is where nodeset serves it from' );
|
||||
ok( -s $target, '... and it is not empty' );
|
||||
|
||||
# The same checks nodeset depends on, against a real grub-mkimage image rather than a fabricated one.
|
||||
my $build = { machine => 0x5064, format => 'riscv64-efi', package => 'grub-efi-riscv64-bin' };
|
||||
ok( xCAT_plugin::debian::_is_uefi_image( $target, $build->{machine} ),
|
||||
'the image is a riscv64 UEFI application' );
|
||||
ok( xCAT_plugin::debian::_is_netboot_loader( $target, $build ),
|
||||
'... carrying the prefix and the modules a net boot needs' );
|
||||
|
||||
# A second import must not rebuild it: the image already in place is the one the nodes booted.
|
||||
my $before = ( stat($target) )[9];
|
||||
my $again = xCAT_plugin::debian::install_media_grub2_loader( $media, 'riscv64', undef );
|
||||
is( $again, $target, 'a second import returns the loader already in place' );
|
||||
is( ( stat($target) )[9], $before, '... without rewriting it' );
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Spec;
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# archive.ubuntu.com publishes amd64 and i386 only. A ppc64el or riscv64 netboot image built
|
||||
# against it finds no package at all, and debootstrap fails before it copies anything, so the
|
||||
# default mirror has to follow the image architecture.
|
||||
#
|
||||
# Driven by the real selection code: the two statements are extracted from genimage and
|
||||
# evaluated here, so this test tracks the script rather than a copy of it.
|
||||
|
||||
my $repo_root = File::Spec->rel2abs( File::Spec->catdir( $FindBin::Bin, '..', '..' ) );
|
||||
use lib "$FindBin::Bin/../../perl-xCAT";
|
||||
require xCAT::Utils;
|
||||
my $genimage_path = File::Spec->catfile(
|
||||
$repo_root, 'xCAT-server', 'share', 'xcat', 'netboot', 'ubuntu', 'genimage'
|
||||
);
|
||||
plan skip_all => "genimage not found at $genimage_path" unless -f $genimage_path;
|
||||
|
||||
my $src = do { local $/; open my $fh, '<', $genimage_path or die $!; <$fh> };
|
||||
|
||||
my ($default) = $src =~ /^\s*(my \$default = \(\$uarch =~.*?;)\s*$/ms;
|
||||
ok( defined $default, 'found the default mirror selection in genimage' )
|
||||
or done_testing(), exit;
|
||||
|
||||
my ($pick) = $src =~ /^\s*(my \$mirror = \(defined \$aptmirror\[0\].*?;)\s*$/ms;
|
||||
ok( defined $pick, 'found the mirror override in genimage' )
|
||||
or done_testing(), exit;
|
||||
|
||||
# genimage does not read osimage.osarch directly: it converts it first, so the value the selection
|
||||
# sees is whatever xCAT::Utils::debian_arch returns. Driving the xCAT token through that conversion
|
||||
# is what catches a token the map does not know.
|
||||
sub choose_osarch {
|
||||
my ( $osarch, $site ) = @_;
|
||||
return choose( xCAT::Utils->debian_arch($osarch), $site );
|
||||
}
|
||||
|
||||
sub choose {
|
||||
my ( $uarch, $site ) = @_;
|
||||
my $set = defined $site ? "('$site')" : "()";
|
||||
## no critic (BuiltinFunctions::ProhibitStringyEval)
|
||||
my $mirror = eval "my \$uarch = '$uarch'; my \@aptmirror = $set; $default $pick \$mirror";
|
||||
## use critic
|
||||
die "failed to evaluate the genimage mirror selection: $@" if $@;
|
||||
return $mirror;
|
||||
}
|
||||
|
||||
is( choose('riscv64'), 'http://ports.ubuntu.com/ubuntu-ports',
|
||||
'a riscv64 image takes the ports archive' );
|
||||
is( choose('ppc64el'), 'http://ports.ubuntu.com/ubuntu-ports',
|
||||
'a ppc64el image takes the ports archive' );
|
||||
is( choose('amd64'), 'http://archive.ubuntu.com/ubuntu',
|
||||
'an amd64 image keeps the main archive' );
|
||||
is( choose('i386'), 'http://archive.ubuntu.com/ubuntu',
|
||||
'an i386 image keeps the main archive' );
|
||||
|
||||
is( choose( 'riscv64', 'http://mirror.example.invalid/ubuntu' ),
|
||||
'http://mirror.example.invalid/ubuntu',
|
||||
'site.ubuntu_apt_mirror overrides the ports archive' );
|
||||
is( choose( 'amd64', 'http://mirror.example.invalid/ubuntu' ),
|
||||
'http://mirror.example.invalid/ubuntu',
|
||||
'site.ubuntu_apt_mirror overrides the main archive' );
|
||||
is( choose( 'riscv64', '' ), 'http://ports.ubuntu.com/ubuntu-ports',
|
||||
'an empty site.ubuntu_apt_mirror does not blank the mirror' );
|
||||
|
||||
# The architecture reaches the selection as an xCAT osarch value, not as a Debian one.
|
||||
is( choose_osarch('x86_64'), 'http://archive.ubuntu.com/ubuntu',
|
||||
'osarch x86_64 reaches the archive' );
|
||||
is( choose_osarch('x86'), 'http://archive.ubuntu.com/ubuntu',
|
||||
'osarch x86 reaches the archive, which is where i386 lives' );
|
||||
is( choose_osarch('riscv64'), 'http://ports.ubuntu.com/ubuntu-ports',
|
||||
'osarch riscv64 reaches the ports archive' );
|
||||
is( choose_osarch('ppc64el'), 'http://ports.ubuntu.com/ubuntu-ports',
|
||||
'osarch ppc64el reaches the ports archive' );
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Path qw(make_path);
|
||||
use File::Spec;
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# A netboot image resolves names with the libnss libraries of its own architecture, and
|
||||
# Ubuntu keeps them in a per-architecture directory. Build a root filesystem for each
|
||||
# architecture and run genimage's selection over it.
|
||||
|
||||
my $repo_root = File::Spec->rel2abs(File::Spec->catdir($FindBin::Bin, '..', '..'));
|
||||
my $genimage = File::Spec->catfile(
|
||||
$repo_root, 'xCAT-server', 'share', 'xcat', 'netboot', 'ubuntu', 'genimage');
|
||||
plan skip_all => "genimage not found at $genimage" unless -f $genimage;
|
||||
|
||||
my $src = do { local $/; open my $fh, '<', $genimage or die $!; <$fh> };
|
||||
my ($selection) =
|
||||
$src =~ /^(\s*if \(\$arch =~ \/x86_64\/\) \{.*?\n\s*\} else \{.*?\n\s*\}\n)/ms;
|
||||
ok(defined $selection, 'found the resolver library selection in genimage')
|
||||
or do { done_testing(); exit };
|
||||
|
||||
sub selected {
|
||||
my ($arch, @present) = @_;
|
||||
my $rootimg_dir = tempdir(CLEANUP => 1);
|
||||
foreach my $file (@present) {
|
||||
my $full = "$rootimg_dir/$file";
|
||||
($full =~ m{^(.*)/[^/]+$}) and make_path($1);
|
||||
open(my $fh, '>', $full) or die $!;
|
||||
close($fh);
|
||||
}
|
||||
my @filestoadd;
|
||||
eval "$selection 1" or die $@; ## no critic (BuiltinFunctions::ProhibitStringyEval)
|
||||
return join(',', sort @filestoadd);
|
||||
}
|
||||
|
||||
is(
|
||||
selected('riscv64', 'lib/riscv64-linux-gnu/libnss_files.so.2',
|
||||
'lib/riscv64-linux-gnu/libnss_dns.so.2'),
|
||||
'lib/riscv64-linux-gnu/libnss_dns.so.2,lib/riscv64-linux-gnu/libnss_files.so.2',
|
||||
'a riscv64 image takes the riscv64 resolver libraries',
|
||||
);
|
||||
is(
|
||||
selected('riscv64', 'lib/libnss_dns.so.2'),
|
||||
'',
|
||||
'riscv64 does not fall back to the path that holds no riscv64 library',
|
||||
);
|
||||
is(
|
||||
selected('x86_64', 'lib/x86_64-linux-gnu/libnss_dns.so.2'),
|
||||
'lib/x86_64-linux-gnu/libnss_dns.so.2',
|
||||
'x86_64 selection is unchanged',
|
||||
);
|
||||
is(
|
||||
selected('ppc64el', 'lib/powerpc64le-linux-gnu/libnss_files.so.2'),
|
||||
'lib/powerpc64le-linux-gnu/libnss_files.so.2',
|
||||
'ppc64el selection is unchanged',
|
||||
);
|
||||
is(
|
||||
selected('s390x', 'lib/libnss_dns.so.2'),
|
||||
'lib/libnss_dns.so.2',
|
||||
'an architecture with no branch keeps the generic library',
|
||||
);
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# Without a driver list the Ubuntu netboot image ships no network module at all, and a node
|
||||
# whose NIC is not built into the kernel cannot reach its root filesystem. QEMU hides this,
|
||||
# because virtio-net is built into the Ubuntu riscv64 kernel; a machine with an r8169 or an
|
||||
# e1000e does not boot.
|
||||
|
||||
use lib "$FindBin::Bin/../../perl-xCAT";
|
||||
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
|
||||
use lib "$FindBin::Bin/../../xCAT-server/share/xcat/netboot/imgutils";
|
||||
require imgutils;
|
||||
|
||||
my @riscv = imgutils::default_net_drivers( 'ubuntu', 'riscv64' );
|
||||
ok( scalar @riscv, 'a riscv64 Ubuntu image is given network drivers' );
|
||||
|
||||
for my $driver (qw(virtio_net e1000 e1000e igb r8169 tg3 mlx5_core)) {
|
||||
ok( scalar( grep { $_ eq $driver } @riscv ), "the list carries $driver" );
|
||||
}
|
||||
|
||||
# Every Ubuntu row carries overlay, because the netboot root is an overlay mount.
|
||||
ok( scalar( grep { $_ eq 'overlay' } @riscv ), 'the list carries overlay' );
|
||||
|
||||
# The architectures that already worked must keep the drivers they had.
|
||||
is_deeply(
|
||||
[ imgutils::default_net_drivers( 'ubuntu', 'x86_64' ) ],
|
||||
[qw(tg3 bnx2 bnx2x e1000 e1000e igb mlx_en mlx5_core virtio_net overlay)],
|
||||
'x86_64 keeps its drivers' );
|
||||
ok( scalar( imgutils::default_net_drivers( 'ubuntu', 'ppc64el' ) ),
|
||||
'ppc64el keeps its drivers' );
|
||||
is_deeply( [ imgutils::default_net_drivers( 'ubuntu', 'sparc' ) ], [],
|
||||
'an architecture with no entry still gets an empty list' );
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Spec;
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# A diskless image needs a kernel and the tools its boot scripts call. Without a package
|
||||
# list for the architecture, genimage debootstraps whatever the generic list holds and the
|
||||
# image cannot boot. Compare the riscv64 lists with the x86_64 ones they follow.
|
||||
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use XCAT::Test::File qw(repo_path);
|
||||
|
||||
sub packages {
|
||||
my ($path) = @_;
|
||||
my $full = repo_path($path);
|
||||
return unless -r $full;
|
||||
open(my $fh, '<', $full) or die "cannot read $full: $!";
|
||||
my @packages = grep { length && !/^#/ } map { my $l = $_; chomp $l; $l =~ s/\s+//g; $l } <$fh>;
|
||||
close($fh);
|
||||
return \@packages;
|
||||
}
|
||||
|
||||
foreach my $release (qw(24.04 26.04)) {
|
||||
my $netboot = "xCAT-server/share/xcat/netboot/ubuntu/compute.ubuntu$release.riscv64.pkglist";
|
||||
my $x86 = "xCAT-server/share/xcat/netboot/ubuntu/compute.ubuntu$release.x86_64.pkglist";
|
||||
my $riscv_packages = packages($netboot);
|
||||
ok($riscv_packages, "$release has a riscv64 netboot package list");
|
||||
is_deeply($riscv_packages, packages($x86),
|
||||
"the $release riscv64 image installs what the x86_64 image installs");
|
||||
ok(scalar(grep { $_ eq 'linux-image-generic' } @{$riscv_packages}),
|
||||
"the $release riscv64 image installs a kernel");
|
||||
ok(scalar(grep { $_ eq 'nfs-common' } @{$riscv_packages}),
|
||||
"the $release riscv64 image can mount its root over NFS");
|
||||
}
|
||||
|
||||
my $install = packages(
|
||||
'xCAT-server/share/xcat/install/ubuntu/compute.ubuntu26.04.riscv64.pkglist');
|
||||
ok($install, '26.04 has a riscv64 install package list');
|
||||
is_deeply($install,
|
||||
packages('xCAT-server/share/xcat/install/ubuntu/compute.ubuntu26.04.x86_64.pkglist'),
|
||||
'the 26.04 riscv64 install takes what the x86_64 install takes');
|
||||
|
||||
done_testing();
|
||||
@@ -39,13 +39,20 @@ sub resolved {
|
||||
}
|
||||
|
||||
# The shared lists keep ntp for the releases that still carry it. aarch64 has no list of its own.
|
||||
foreach my $case ( [ $install, 'compute' ], [ $install, 'service' ], [ $install, 'kvm' ], [ $netboot, 'compute' ] ) {
|
||||
# The install compute profile has a 24.04 list of its own, with chrony: the Subiquity template
|
||||
# installs chrony and ntp cannot join it in one apt transaction (ubuntu_subiquity_pkglists.t).
|
||||
foreach my $case ( [ $install, 'service' ], [ $install, 'kvm' ], [ $netboot, 'compute' ] ) {
|
||||
my ( $dir, $profile ) = @$case;
|
||||
my ( $file, $p ) = resolved( $dir, $profile, 'ubuntu24.04.4', 'aarch64' );
|
||||
is( $file, "$profile.pkglist", "$profile on 24.04 without a list of its own resolves to the shared list" );
|
||||
ok( $p->{ntp}, "... which keeps ntp" );
|
||||
ok( !$p->{$_}, "... and no longer names $_" ) for qw(libodbc1 qemu-kvm libvirt-bin);
|
||||
}
|
||||
{
|
||||
my ( $file, $p ) = resolved( $install, 'compute', 'ubuntu24.04.4', 'aarch64' );
|
||||
is( $file, 'compute.ubuntu24.04.pkglist', 'the install compute profile on 24.04 resolves its own list' );
|
||||
ok( $p->{chrony} && !$p->{ntp}, '... which names chrony and not ntp' );
|
||||
}
|
||||
ok( packages_in("$install/service.pkglist")->{unixodbc}, 'the shared service list names unixodbc' );
|
||||
ok( packages_in("$install/service.pkglist")->{'libdbd-pg-perl'}, '... and the PostgreSQL driver beside the MySQL one' );
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# archive.ubuntu.com publishes amd64 and i386 only, so a ppc64el or riscv64 stateful install
|
||||
# is handed a mirror that carries no package for it. The architecture being installed is the
|
||||
# last component of the media directory.
|
||||
|
||||
BEGIN {
|
||||
package xCAT::Table;
|
||||
our $value;
|
||||
sub new { return bless {}, shift }
|
||||
sub getAttribs { return defined $value ? { value => $value } : undef }
|
||||
$INC{'xCAT/Table.pm'} = __FILE__;
|
||||
}
|
||||
|
||||
use lib "$FindBin::Bin/../../perl-xCAT";
|
||||
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
|
||||
my $module = "$FindBin::Bin/../../xCAT-server/lib/perl/xCAT/Template.pm";
|
||||
plan skip_all => 'Template.pm not found' unless -r $module;
|
||||
eval { require $module; 1 } or plan skip_all => "could not load Template.pm: $@";
|
||||
|
||||
my $mirror = \&xCAT::Template::ubuntu_subiquity_apt_mirror;
|
||||
|
||||
# The osimage's architecture decides the mirror. pkgdir cannot: it is whatever path the
|
||||
# administrator configured, so an amd64 image under /srv/custom-media would be read as a
|
||||
# non-x86 architecture and sent to the ports archive.
|
||||
is( $mirror->('riscv64'), 'http://ports.ubuntu.com/ubuntu-ports',
|
||||
'a riscv64 image takes the ports archive' );
|
||||
is( $mirror->('ppc64el'), 'http://ports.ubuntu.com/ubuntu-ports',
|
||||
'a ppc64el image takes the ports archive' );
|
||||
is( $mirror->('ppc64le'), 'http://ports.ubuntu.com/ubuntu-ports',
|
||||
'the other spelling of ppc64 takes the ports archive' );
|
||||
|
||||
for my $x86 (qw(x86_64 amd64 x86 i386)) {
|
||||
is( $mirror->($x86), 'http://archive.ubuntu.com/ubuntu',
|
||||
"an $x86 image keeps the main archive" );
|
||||
}
|
||||
|
||||
# With no architecture there is nothing to key on, so the previous default stands.
|
||||
is( $mirror->(undef), 'http://archive.ubuntu.com/ubuntu',
|
||||
'an unknown architecture keeps the previous default' );
|
||||
|
||||
# site.ubuntu_apt_mirror still wins, which is how an airgapped cluster points at its own.
|
||||
{
|
||||
local $xCAT::Table::value = 'http://mirror.example.invalid/ubuntu';
|
||||
is( $mirror->('riscv64'), 'http://mirror.example.invalid/ubuntu',
|
||||
'site.ubuntu_apt_mirror overrides the ports archive' );
|
||||
is( $mirror->('x86_64'), 'http://mirror.example.invalid/ubuntu',
|
||||
'site.ubuntu_apt_mirror overrides the main archive' );
|
||||
}
|
||||
{
|
||||
local $xCAT::Table::value = '';
|
||||
is( $mirror->('riscv64'), 'http://ports.ubuntu.com/ubuntu-ports',
|
||||
'an empty site.ubuntu_apt_mirror does not blank the mirror' );
|
||||
}
|
||||
|
||||
done_testing();
|
||||
@@ -21,11 +21,32 @@ sub apt_config_for {
|
||||
no warnings 'redefine';
|
||||
local *xCAT::Template::ubuntu_subiquity_apt_mirror = sub { $opt{mirror} };
|
||||
local *xCAT::Template::ubuntu_subiquity_uses_deb822_sources = sub { $opt{deb822} };
|
||||
local *xCAT::Template::ubuntu_subiquity_otherpkg_sources = sub { () };
|
||||
local *xCAT::Template::ubuntu_subiquity_otherpkg_sources = sub { @{ $opt{others} || [] } };
|
||||
local *xCAT::Template::ubuntu_subiquity_uses_generated_cdrom_source = sub { 0 };
|
||||
return xCAT::Template::ubuntu_subiquity_apt_config('/some/media/dir');
|
||||
}
|
||||
|
||||
# --- the otherpkgs repository: a one-line source before Deb822, a Deb822 stanza from 24.04 on, ---
|
||||
# --- since curtin drops the options of a one-line source when it converts it there ---
|
||||
my $others = [ 'http://192.0.2.10/install/post/otherpkgs/ubuntu24.04/x86_64' ];
|
||||
my $classic_others = apt_config_for( mirror => $MIRROR, deb822 => 0, others => $others );
|
||||
like( $classic_others, qr{^ xcat-otherpkgs-0\.list:\n source: "deb \[trusted=yes\] http://192\.0\.2\.10/install/post/otherpkgs/ubuntu24\.04/x86_64 \./"$}m,
|
||||
'classic: the otherpkgs repository is a one-line trusted source' );
|
||||
my $deb822_others = apt_config_for( mirror => $MIRROR, deb822 => 1, others => $others );
|
||||
like( $deb822_others,
|
||||
qr{^ xcat-otherpkgs-0\.sources:\n source: \|\n Types: deb\n URIs: http://192\.0\.2\.10/install/post/otherpkgs/ubuntu24\.04/x86_64\n Suites: \./\n Components:\n Trusted: yes(?:\n|\z)}m,
|
||||
'Deb822: the otherpkgs repository is a Deb822 stanza carrying Trusted: yes' );
|
||||
unlike( $deb822_others, qr/xcat-otherpkgs-0\.list|trusted=yes/, 'Deb822: and no one-line form remains' );
|
||||
|
||||
# an otherpkgdir written as URL, suite and components is that source, trusted, not a flat repository at a URL with spaces
|
||||
my $mirror_others = [ 'http://mirror.example/ubuntu noble main universe' ];
|
||||
like( apt_config_for( mirror => $MIRROR, deb822 => 0, others => $mirror_others ),
|
||||
qr{^ xcat-otherpkgs-0\.list:\n source: "deb \[trusted=yes\] http://mirror\.example/ubuntu noble main universe"$}m,
|
||||
'classic: an otherpkgdir mirror entry keeps its suite and components' );
|
||||
like( apt_config_for( mirror => $MIRROR, deb822 => 1, others => $mirror_others ),
|
||||
qr{^ xcat-otherpkgs-0\.sources:\n source: \|\n Types: deb\n URIs: http://mirror\.example/ubuntu\n Suites: noble\n Components: main universe\n Trusted: yes(?:\n|\z)}m,
|
||||
'Deb822: and becomes a stanza with them as fields, so apt reads one URI' );
|
||||
|
||||
# --- online, classic sources (20.04 / 22.04): the archive must be added via sources: ---
|
||||
my $classic = apt_config_for( mirror => $MIRROR, deb822 => 0 );
|
||||
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Spec;
|
||||
use File::Temp;
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# ospkgs installs the pkglist from every pkgdir entry, the media first and the mirrors after it,
|
||||
# and the Subiquity autoinstall now installs the pkglist too, so the installer must be given the
|
||||
# same mirrors. The specs are derived from the pkgdir value alone, and the apt configuration is
|
||||
# rendered for real with only the database readers stubbed.
|
||||
|
||||
my $repo = File::Spec->rel2abs( File::Spec->catdir( $FindBin::Bin, '..', '..' ) );
|
||||
my @incs = ( "$repo/perl-xCAT", "$repo/xCAT-server/lib/perl" );
|
||||
my $devnull = File::Spec->devnull();
|
||||
my $probe = join( ' ', $^X, ( map { "-I$_" } @incs ), '-e', "'require xCAT::Template; 1'", ">$devnull", "2>&1" );
|
||||
plan skip_all => 'xCAT::Template cannot be loaded here' if system($probe) != 0;
|
||||
require lib;
|
||||
lib->import(@incs);
|
||||
require xCAT::Template;
|
||||
|
||||
{
|
||||
no warnings qw(redefine once);
|
||||
*xCAT::Template::ubuntu_subiquity_pkgdir_uri = sub { return "http://192.0.2.10$_[0]" };
|
||||
}
|
||||
my $flat = File::Temp->newdir();
|
||||
open( my $pfh, '>', "$flat/Packages" ) or die $!; close $pfh;
|
||||
open( my $rfh, '>', "$flat/Release" ) or die $!; close $rfh;
|
||||
my @specs = xCAT::Template::ubuntu_subiquity_pkgdir_source_specs(
|
||||
"/install/ubuntu24.04.4/x86_64, http://mirror.example/ubuntu noble main universe ,http://repo.example/extra,ssh://host/path,http://mirror.example/ubuntu jammy,http://flat.example/pool ./,$flat"
|
||||
);
|
||||
is( scalar(@specs), 3, 'the media path, the bare URL, the ssh entry and a suite without components are no apt sources; the rest are' );
|
||||
is_deeply( $specs[0], { uri => 'http://mirror.example/ubuntu', suites => 'noble', components => 'main universe', trusted => 0, signed_by => '',
|
||||
line => 'deb http://mirror.example/ubuntu noble main universe' },
|
||||
'an entry written as URL suite components is used as written, and is not marked trusted' );
|
||||
is_deeply( $specs[1], { uri => 'http://flat.example/pool', suites => './', components => '', trusted => 0, signed_by => '',
|
||||
line => 'deb http://flat.example/pool ./' },
|
||||
'an exact-path suite needs no component' );
|
||||
is_deeply( $specs[2], { uri => "http://192.0.2.10$flat", suites => './', components => '', trusted => 1, signed_by => '',
|
||||
line => "deb [trusted=yes] http://192.0.2.10$flat ./" },
|
||||
'a local flat repository is served by the management node and trusted, as an otherpkgdir is' );
|
||||
is_deeply( [ xCAT::Template::ubuntu_subiquity_pkgdir_source_specs(undef) ], [], 'no pkgdir gives no sources' );
|
||||
my @alias = xCAT::Template::ubuntu_subiquity_pkgdir_source_specs("$flat,http://192.0.2.10$flat/ ./");
|
||||
is( scalar(@alias), 1, 'a local repository and its own URL in pkgdir are one source' );
|
||||
is( $alias[0]{trusted}, 1, '... trusted, as the directory entry is' );
|
||||
my $keyring = '/usr/share/keyrings/ubuntu-archive-keyring.gpg';
|
||||
is_deeply(
|
||||
[ xCAT::Template::ubuntu_subiquity_pkgdir_source_specs( 'http://archive.example/ubuntu/ noble-proposed main,http://mirror.example/ubuntu noble main', $keyring, 'http://archive.example/ubuntu' ) ],
|
||||
[ { uri => 'http://archive.example/ubuntu/', suites => 'noble-proposed', components => 'main', trusted => 0, signed_by => $keyring,
|
||||
line => "deb [signed-by=$keyring] http://archive.example/ubuntu/ noble-proposed main" },
|
||||
{ uri => 'http://mirror.example/ubuntu', suites => 'noble', components => 'main', trusted => 0, signed_by => '', line => 'deb http://mirror.example/ubuntu noble main' } ],
|
||||
'an entry that names the apt mirror, trailing slash or not, carries the key given for that mirror; another mirror gets none' );
|
||||
is_deeply(
|
||||
[ xCAT::Template::ubuntu_subiquity_pkgdir_source_specs( 'http://archive.example/ubuntu jammy main', '', 'http://archive.example/ubuntu' ) ],
|
||||
[ { uri => 'http://archive.example/ubuntu', suites => 'jammy', components => 'main', trusted => 0, signed_by => '', line => 'deb http://archive.example/ubuntu jammy main' } ],
|
||||
'without a key for the mirror the entry is used as written' );
|
||||
|
||||
our ( $apt_mirror, @otherpkg_sources ) = ( '', () );
|
||||
{
|
||||
no warnings qw(redefine once);
|
||||
*xCAT::Template::ubuntu_subiquity_apt_mirror = sub { return $main::apt_mirror };
|
||||
*xCAT::Template::ubuntu_subiquity_otherpkg_sources = sub { return @main::otherpkg_sources };
|
||||
}
|
||||
# the value mkinstall hands over: the media first, then two mirrors
|
||||
my $mirrors = '/install/ubuntu24.04.4/x86_64,http://mirror.example/ubuntu noble main,http://repo.example/extra';
|
||||
|
||||
sub apt_config_for {
|
||||
my ( $media_dir, %args ) = @_;
|
||||
local $apt_mirror = $args{mirror} // '';
|
||||
local @otherpkg_sources = @{ $args{others} || [] };
|
||||
return xCAT::Template::ubuntu_subiquity_apt_config( $media_dir, undef, $args{pkgdirs} );
|
||||
}
|
||||
|
||||
my $online = apt_config_for( 'ubuntu24.04', mirror => 'http://archive.example/ubuntu', pkgdirs => $mirrors );
|
||||
like( $online, qr/^ sources:$/m, 'online: the pkgdir mirrors open the sources mapping' );
|
||||
like( $online, qr{^ xcat-pkgdir-0\.sources:\n source: \|\n Types: deb\n URIs: http://mirror\.example/ubuntu\n Suites: noble\n Components: main(?:\n|\z)}m,
|
||||
'... as a Deb822 stanza on a Deb822 release, with the suite and components as written' );
|
||||
unlike( $online, qr{xcat-pkgdir-1|repo\.example}, '... and the bare URL is not offered as a repository' );
|
||||
|
||||
my $online_both = apt_config_for( 'ubuntu22.04', mirror => 'http://archive.example/ubuntu', others => ['http://mn/otherpkgs'], pkgdirs => $mirrors );
|
||||
is( scalar( () = $online_both =~ /^ sources:$/mg ), 1, 'online with otherpkgs and pkgdir mirrors: one sources mapping' );
|
||||
like( $online_both, qr/xcat-otherpkgs-0\.list:.*xcat-pkgdir-0\.list:/s, '... otherpkgs first, then the mirror' );
|
||||
|
||||
my $offline_deb822 = apt_config_for( 'ubuntu24.04', pkgdirs => $mirrors );
|
||||
like( $offline_deb822, qr{^ URIs: http://mirror\.example/ubuntu\n Suites: noble\n Components: main(?:\n|\z)}m, 'offline Deb822: a stanza per mirror' );
|
||||
unlike( $offline_deb822, qr{repo\.example|Trusted: yes\n(?:.*\n)* URIs: http://mirror}, '... nothing trusted and no bare URL' );
|
||||
|
||||
my $offline_legacy = apt_config_for( 'ubuntu22.04', pkgdirs => $mirrors );
|
||||
like( $offline_legacy, qr{^ sources:\n xcat-pkgdir-0\.list:\n source: "deb http://mirror\.example/ubuntu noble main"$}m, 'offline legacy: .list files under sources' );
|
||||
|
||||
my $same = apt_config_for( 'ubuntu24.04', mirror => 'http://mirror.example/ubuntu', pkgdirs => 'http://mirror.example/ubuntu noble-proposed main' );
|
||||
like( $same, qr{^ xcat-pkgdir-0\.sources:\n source: \|\n Types: deb\n URIs: http://mirror\.example/ubuntu\n Suites: noble-proposed\n Components: main\n Signed-By: \Q$keyring\E(?:\n|\z)}m,
|
||||
'another suite of the apt mirror is added with the signing key the installer gives that mirror' );
|
||||
|
||||
# before Deb822 the archive sources are rendered here without a key, so a repeated mirror entry must carry none either
|
||||
foreach my $release ( [ 'ubuntu20.04', 'focal' ], [ 'ubuntu22.04', 'jammy' ] ) {
|
||||
my ( $media, $suite ) = @$release;
|
||||
my $legacy_same = apt_config_for( $media, mirror => 'http://mirror.example/ubuntu', pkgdirs => "http://mirror.example/ubuntu $suite main" );
|
||||
like( $legacy_same, qr{^ xcat-pkgdir-0\.list:\n source: "deb http://mirror\.example/ubuntu \Q$suite\E main"$}m,
|
||||
"$media: an entry that repeats the apt mirror carries no signing key, like the archive sources rendered next to it" );
|
||||
like( $legacy_same, qr{^ xcat-ubuntu-archive\.list:\n source: "deb http://mirror\.example/ubuntu \$RELEASE main restricted universe multiverse"$}m,
|
||||
"$media: which stay as they were" );
|
||||
}
|
||||
|
||||
# a repository named by otherpkgdir and by pkgdir is offered once, trusted, on both source forms
|
||||
foreach my $media ( 'ubuntu24.04', 'ubuntu22.04' ) {
|
||||
my $overlap = apt_config_for( $media, mirror => 'http://archive.example/ubuntu', others => ['http://repo.example/ubuntu'],
|
||||
pkgdirs => '/install/ubuntu24.04.4/x86_64,http://repo.example/ubuntu/ ./' );
|
||||
like( $overlap, qr/xcat-otherpkgs-0\./, "$media: the repository the otherpkgdir names is offered as the trusted otherpkgs source" );
|
||||
unlike( $overlap, qr/xcat-pkgdir/, "$media: and not again as a pkgdir source with other options" );
|
||||
}
|
||||
|
||||
# an otherpkgdir entry that names the apt mirror itself gets the options of the source the installer already has for it
|
||||
my $archive_others = ['http://mirror.example/ubuntu noble universe'];
|
||||
like( apt_config_for( 'ubuntu22.04', mirror => 'http://mirror.example/ubuntu', others => $archive_others ),
|
||||
qr{^ xcat-otherpkgs-0\.list:\n source: "deb http://mirror\.example/ubuntu noble universe"$}m,
|
||||
'classic: an otherpkgdir entry for the apt mirror carries no option, like the archive sources next to it' );
|
||||
like( apt_config_for( 'ubuntu24.04', mirror => 'http://mirror.example/ubuntu', others => $archive_others ),
|
||||
qr{^ xcat-otherpkgs-0\.sources:\n source: \|\n Types: deb\n URIs: http://mirror\.example/ubuntu\n Suites: noble\n Components: universe\n Signed-By: \Q$keyring\E(?:\n|\z)}m,
|
||||
'Deb822: and carries the archive keyring and no Trusted, as the primary source does' );
|
||||
|
||||
# the same repository and suite with other components: the pkgdir components join the otherpkgs source
|
||||
my $union = apt_config_for( 'ubuntu24.04', mirror => 'http://archive.example/ubuntu', others => ['http://repo.example/team stable tools'],
|
||||
pkgdirs => '/install/ubuntu24.04.4/x86_64,http://repo.example/team/ stable compute tools' );
|
||||
like( $union, qr{^ xcat-otherpkgs-0\.sources:\n source: \|\n Types: deb\n URIs: http://repo\.example/team\n Suites: stable\n Components: tools compute\n Trusted: yes(?:\n|\z)}m,
|
||||
'a pkgdir entry that repeats an otherpkgs repository adds its components to that source' );
|
||||
unlike( $union, qr/xcat-pkgdir/, '... and is not a second source' );
|
||||
my $union_legacy = apt_config_for( 'ubuntu22.04', mirror => 'http://archive.example/ubuntu', others => ['http://repo.example/team stable tools'],
|
||||
pkgdirs => '/install/ubuntu24.04.4/x86_64,http://repo.example/team stable compute' );
|
||||
like( $union_legacy, qr{^ xcat-otherpkgs-0\.list:\n source: "deb \[trusted=yes\] http://repo\.example/team stable tools compute"$}m,
|
||||
'in the one-line form as well' );
|
||||
|
||||
my $none = apt_config_for( 'ubuntu24.04', mirror => 'http://archive.example/ubuntu' );
|
||||
unlike( $none, qr/sources:/, 'without otherpkgs or mirrors no sources mapping is rendered on a Deb822 release' );
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
no warnings 'once';
|
||||
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use File::Spec;
|
||||
use File::Temp;
|
||||
use Test::More;
|
||||
|
||||
use XCAT::Test::File qw(repo_path);
|
||||
|
||||
sub read_text { my ($path) = @_; open( my $fh, '<', $path ) or die "$path: $!"; local $/; my $text = <$fh>; close($fh); return $text; }
|
||||
sub write_text { my ( $path, $text ) = @_; open( my $fh, '>', $path ) or die "$path: $!"; print {$fh} $text; close($fh); return; }
|
||||
|
||||
# A Subiquity autoinstall installs the packages of its user-data packages list, and until now the
|
||||
# template named a fixed set, so the osimage pkglist reached the node only through ospkgs after
|
||||
# the first boot. The template can now carry #INCLUDE_DEFAULT_PKGLIST_AUTOINSTALL# on a list line,
|
||||
# and Template.pm renders one list item per pkglist package in its place.
|
||||
|
||||
my $module = repo_path('xCAT-server/lib/perl/xCAT/Template.pm');
|
||||
plan skip_all => 'Template.pm not found' unless -r $module;
|
||||
|
||||
my @incs = ( repo_path('perl-xCAT'), repo_path('xCAT-server/lib/perl') );
|
||||
|
||||
# Every xCAT module prepends $XCATROOT/lib/perl as it compiles, so on a host with xCAT installed
|
||||
# the modules Template.pm loads afterwards would come from /opt/xcat. Point it at the checkout.
|
||||
my $xcatroot = File::Temp->newdir();
|
||||
mkdir "$xcatroot/lib" or die "$xcatroot/lib: $!";
|
||||
symlink( repo_path('xCAT-server/lib/perl'), "$xcatroot/lib/perl" ) or die "symlink: $!";
|
||||
$ENV{XCATROOT} = "$xcatroot";
|
||||
|
||||
my $devnull = File::Spec->devnull();
|
||||
my $probe = join( ' ', $^X, ( map { "-I$_" } @incs ), '-e', "'require xCAT::Template; 1'", ">$devnull", "2>&1" );
|
||||
plan skip_all => 'xCAT::Template cannot be loaded here' if system($probe) != 0;
|
||||
|
||||
require lib;
|
||||
lib->import(@incs);
|
||||
require xCAT::Template;
|
||||
require xCAT::Postage; # the pkglist reader Template.pm calls, loaded by the plugin in production
|
||||
|
||||
# ---- the record filter: only what apt can be asked for in an autoinstall packages list --------
|
||||
my @packages = xCAT::Template::ubuntu_autoinstall_packages(
|
||||
'openssh-server', ' gawk', 'ntp', '-snmpd', '@core', '#NEW_INSTALL_LIST#', 'd-i pkgsel/include string foo',
|
||||
'd-i tasksel/first multiselect standard,not-a-real-package', 'nfs-common=1:2.6.4-3ubuntu5', 'gawk', 'Bad_Name', '',
|
||||
'libc6', 'libc6:i386', 'curl/noble', 'dns-server^', 'a/b/c', 'vim rsync -busybox-static gpg', '@Group With Space', 'wget Bad_Name', 'bc # a calculator',
|
||||
'wget-', 'libc6:i386-', 'tree+', '-snmpd apache2', '@core bc'
|
||||
);
|
||||
is_deeply( \@packages, [qw(openssh-server gawk ntp libc6 dns-server^ vim rsync gpg bc tree+)],
|
||||
'names and tasks are kept once each, a space-separated line gives each package, a comment ends it; pins, target releases, architecture qualifiers, removals in either hyphen form, a record that begins with a removal or a group, markers, directives and unknown syntax are not' );
|
||||
is_deeply( [ xCAT::Template::ubuntu_autoinstall_packages( 'msodbcsql18', '#ENV:ACCEPT_EULA=Y#', 'gawk' ) ], [],
|
||||
'a list with an apt environment setting stays with ospkgs whole' );
|
||||
is_deeply( [ xCAT::Template::ubuntu_autoinstall_packages( 'gawk', 'msodbcsql18 #ENV:ACCEPT_EULA=Y#' ) ], [],
|
||||
'so does a list with the setting after a package on the same line, where get_envlist reads it too' );
|
||||
is_deeply( [ xCAT::Template::ubuntu_autoinstall_packages( 'gawk', '#INCLUDEBAD:cannot open pkglist file /absent.pkglist#' ) ], [],
|
||||
'a list with an unreadable include stays with ospkgs whole' );
|
||||
is_deeply( [ xCAT::Template::ubuntu_autoinstall_packages() ], [], 'no records give no packages' );
|
||||
|
||||
# ---- the record reader: lines kept whole, includes followed, the comma text unchanged ---------
|
||||
{
|
||||
my $d = File::Temp->newdir();
|
||||
write_text( "$d/common.pkglist", "# shared\nnfs-common\n\@Group With Space\n" );
|
||||
write_text( "$d/compute.pkglist", "openssh-server\n # a comment\nd-i tasksel/first multiselect standard,not-a-real-package\n#INCLUDE:$d/common.pkglist#\n#NEW_INSTALL_LIST#\nchrony\n" );
|
||||
my @records = xCAT::Postage->get_pkglist_records("$d/compute.pkglist");
|
||||
is_deeply( \@records,
|
||||
[ 'openssh-server', 'd-i tasksel/first multiselect standard,not-a-real-package', 'nfs-common', '@Group With Space', '#NEW_INSTALL_LIST#', 'chrony' ],
|
||||
'records are whole lines, comments dropped, the include expanded in place' );
|
||||
is( xCAT::Postage->get_pkglist_tex("$d/compute.pkglist"),
|
||||
'openssh-server,d-i tasksel/first multiselect standard,not-a-real-package,nfs-common,@Group With Space,#NEW_INSTALL_LIST#,chrony',
|
||||
'the comma text ospkgs receives is unchanged, and cannot tell the directive comma apart' );
|
||||
my @missing = xCAT::Postage->get_pkglist_records("$d/absent.pkglist");
|
||||
like( $missing[0], qr/^#INCLUDEBAD:/, 'an unreadable file yields the INCLUDEBAD marker record' );
|
||||
|
||||
# top.pkglist includes sub/common.pkglist, which includes leaf.pkglist: the leaf next to top.pkglist is the one meant
|
||||
mkdir "$d/sub" or die "$d/sub: $!";
|
||||
write_text( "$d/top.pkglist", "#INCLUDE:sub/common.pkglist#\n" );
|
||||
write_text( "$d/sub/common.pkglist", "#INCLUDE:leaf.pkglist#\n" );
|
||||
write_text( "$d/leaf.pkglist", "nfs-common\n" );
|
||||
write_text( "$d/sub/leaf.pkglist", "snmpd\n" );
|
||||
is_deeply( [ xCAT::Postage->get_pkglist_records("$d/top.pkglist") ], ['nfs-common'],
|
||||
'a nested include resolves against the directory of the listed pkglist' );
|
||||
is_deeply( [ xCAT::Postage->get_pkglist_records("$d/top.pkglist") ], [ split /,/, xCAT::Postage->get_pkglist_tex("$d/top.pkglist") ],
|
||||
'and reads the same files get_pkglist_tex reads' );
|
||||
|
||||
write_text( "$d/note.pkglist", "#INCLUDE:leaf.pkglist# # the shared leaf\nbc # a calculator\n" );
|
||||
my @noted = xCAT::Postage->get_pkglist_records("$d/note.pkglist");
|
||||
is_deeply( \@noted, [ split /,/, xCAT::Postage->get_pkglist_tex("$d/note.pkglist") ],
|
||||
'an include followed by a note is expanded, the note staying on the last record as get_pkglist_tex leaves it' );
|
||||
is_deeply( [ xCAT::Template::ubuntu_autoinstall_packages(@noted) ], [qw(nfs-common bc)], 'and the notes add no packages' );
|
||||
}
|
||||
is( xCAT::Template::ubuntu_autoinstall_items( " ", " - wget\n - gpg\n", [qw(gawk gpg chrony)] ),
|
||||
" - \"gawk\"\n - \"chrony\"\n", 'items already listed above the token are not repeated, and every item is a quoted string' );
|
||||
is( xCAT::Template::ubuntu_autoinstall_items( " ", "", [qw(null true 12)] ), " - \"null\"\n - \"true\"\n - \"12\"\n",
|
||||
'names YAML would read as null, boolean or number stay strings' );
|
||||
is( xCAT::Template::ubuntu_autoinstall_items( " ", " - wget\n - chrony\n", [qw(gawk ntp ntpdate snmpd)] ),
|
||||
" - \"gawk\"\n - \"snmpd\"\n", 'a time daemon the template installs keeps the pkglist time daemons with ospkgs' );
|
||||
is( xCAT::Template::ubuntu_autoinstall_items( " ", " - wget\n", [qw(gawk ntp)] ),
|
||||
" - \"gawk\"\n - \"ntp\"\n", 'without a fixed time daemon the pkglist one is installed' );
|
||||
|
||||
# ---- the rendering: the token line becomes one item per package, at its own indentation -------
|
||||
my %site;
|
||||
no warnings 'redefine', 'once';
|
||||
local *xCAT::TableUtils::get_site_attribute = sub {
|
||||
my ( undef, $key ) = @_;
|
||||
return defined $site{$key} ? ( $site{$key} ) : ();
|
||||
};
|
||||
local *xCAT::NetworkUtils::getipaddr = sub { return '192.0.2.10'; };
|
||||
local *xCAT::Template::getPersistentKcmdline = sub { return ''; };
|
||||
use warnings;
|
||||
|
||||
my $dir = File::Temp->newdir();
|
||||
my $included = File::Spec->catfile( "$dir", 'common.pkglist' );
|
||||
my $pkglist = File::Spec->catfile( "$dir", 'compute.pkglist' );
|
||||
write_text( $included, "# shared\nnfs-common\nsnmpd\n" );
|
||||
write_text( $pkglist, "openssh-server\n# a comment\nchrony rsync # time and files\nwget=1.21.2-2ubuntu1\n-ntp\nwget-\n\@standard\nd-i tasksel/first multiselect standard,not-a-real-package\n#INCLUDE:$included#\n" );
|
||||
|
||||
my $in = File::Spec->catfile( "$dir", 'in.tmpl' );
|
||||
write_text( $in,
|
||||
" packages:\n"
|
||||
. " - openssh-server\n"
|
||||
. " - wget\n"
|
||||
. " - #INCLUDE_DEFAULT_PKGLIST_AUTOINSTALL#\n"
|
||||
. " late-commands:\n"
|
||||
. " - echo done\n" );
|
||||
|
||||
my $render = sub {
|
||||
my ( $list, %extra ) = @_;
|
||||
%site = ( installdir => '/install' );
|
||||
my $out = File::Spec->catfile( "$dir", 'out.' . ( defined $list ? 'list' : 'none' ) );
|
||||
xCAT::Template->subvars( $in, $out, 'testnode', $list, '/install/ubuntu24.04/x86_64', 'ubuntu', undef,
|
||||
{ xcatmaster => '192.0.2.10' }, osarch => 'x86_64', %extra );
|
||||
return read_text($out);
|
||||
};
|
||||
|
||||
my $rendered = $render->($pkglist);
|
||||
is( $rendered,
|
||||
" packages:\n"
|
||||
. " - openssh-server\n"
|
||||
. " - wget\n"
|
||||
. " - \"chrony\"\n"
|
||||
. " - \"rsync\"\n"
|
||||
. " - \"nfs-common\"\n"
|
||||
. " - \"snmpd\"\n"
|
||||
. " late-commands:\n"
|
||||
. " - echo done\n",
|
||||
'the pkglist packages, includes followed, become list items at the token indentation, without repeating the items above' );
|
||||
unlike( $rendered, qr/not-a-real-package/, 'a package name inside a preseed directive with commas is not a package' );
|
||||
unlike( $rendered, qr/wget=/, 'a version pin is not an item: it stays with ospkgs' );
|
||||
unlike( $rendered, qr/wget-/, 'a trailing-hyphen removal is not an item either: the installer would remove the package the postscripts need' );
|
||||
unlike( $rendered, qr/"(?:time|and|files)"/, 'an inline comment adds no items' );
|
||||
|
||||
# a site template that includes the stock one: the token arrives with the include and must be expanded too
|
||||
my $wrapper = File::Spec->catfile( "$dir", 'wrapper.tmpl' );
|
||||
write_text( $wrapper, "#INCLUDE:$in#\n" );
|
||||
my $render_via = sub {
|
||||
my ($list) = @_;
|
||||
%site = ( installdir => '/install' );
|
||||
my $out = File::Spec->catfile( "$dir", 'out.wrapper.' . ( defined $list ? 'list' : 'none' ) );
|
||||
xCAT::Template->subvars( $wrapper, $out, 'testnode', $list, '/install/ubuntu24.04/x86_64', 'ubuntu', undef, { xcatmaster => '192.0.2.10' }, osarch => 'x86_64' );
|
||||
return read_text($out);
|
||||
};
|
||||
is( $render_via->($pkglist), $rendered, 'a template that includes the stock one renders the same package list' );
|
||||
|
||||
my $without = $render->(undef);
|
||||
is( $without,
|
||||
" packages:\n - openssh-server\n - wget\n late-commands:\n - echo done\n",
|
||||
'an osimage without a pkglist keeps the template packages and loses only the token line' );
|
||||
is( $render_via->(undef), $without, 'and through an including template the token line goes as well, leaving no empty item' );
|
||||
|
||||
my $env_list = File::Spec->catfile( "$dir", 'env.pkglist' );
|
||||
my $env_included = File::Spec->catfile( "$dir", 'env-common.pkglist' );
|
||||
write_text( $env_included, "msodbcsql18 #ENV:ACCEPT_EULA=Y#\n" );
|
||||
write_text( $env_list, "gawk\n#INCLUDE:$env_included#\n" );
|
||||
is( $render->($env_list), $without, 'a pkglist whose include carries an apt environment setting is left to ospkgs whole, and the token line goes' );
|
||||
is( $render->( $pkglist, environvar => 'ACCEPT_EULA=Y' ), $without,
|
||||
'an osimage with environvar installs its pkglist through ospkgs alone, where the variables reach apt-get, and the token line goes' );
|
||||
|
||||
# the pkgdir mirrors may need those variables as well, so they stay out of the installer's apt sources too
|
||||
{
|
||||
no warnings 'redefine', 'once';
|
||||
local *xCAT::Template::ubuntu_subiquity_otherpkg_sources = sub { () };
|
||||
local *xCAT::Template::ubuntu_subiquity_apt_mirror = sub { 'http://archive.example/ubuntu' };
|
||||
my $apt_in = File::Spec->catfile( "$dir", 'apt.tmpl' );
|
||||
write_text( $apt_in, "#UBUNTU_SUBIQUITY_APT_CONFIG#\n" );
|
||||
my $apt_render = sub {
|
||||
my (%extra) = @_;
|
||||
%site = ( installdir => '/install' );
|
||||
my $out = File::Spec->catfile( "$dir", 'out.apt' );
|
||||
xCAT::Template->subvars( $apt_in, $out, 'testnode', $pkglist, '/install/ubuntu24.04/x86_64', 'ubuntu', undef, { xcatmaster => '192.0.2.10' },
|
||||
osarch => 'x86_64', pkgdirs => '/install/ubuntu24.04/x86_64,http://mirror.example/ubuntu noble main', %extra );
|
||||
return read_text($out);
|
||||
};
|
||||
like( $apt_render->(), qr{URIs: http://mirror\.example/ubuntu}, 'the pkgdir mirror joins the installer sources' );
|
||||
like( $apt_render->(), qr{^ conf: 'APT::Install-Recommends "false";'$}m, 'the installer installs without recommended packages, as ospkgs does' );
|
||||
unlike( $apt_render->( environvar => 'http_proxy=http://proxy.example:3128' ), qr{mirror\.example|xcat-pkgdir},
|
||||
'but not for an osimage with environvar, whose mirrors may need those variables' );
|
||||
}
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Spec;
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
use lib "$FindBin::Bin/../../perl-xCAT";
|
||||
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
|
||||
use xCAT::Postage;
|
||||
use xCAT::SvrUtils;
|
||||
|
||||
# The Subiquity autoinstall installs the template's fixed packages and the osimage pkglist in one
|
||||
# apt transaction, and the template names chrony. The shared compute.pkglist names ntp for the
|
||||
# releases before Subiquity, and on the Subiquity releases ntp pulls ntpsec, which conflicts with
|
||||
# chrony, so those releases need their own default list. The lists are resolved the way
|
||||
# mkinstall resolves them and their packages are read the way ospkgs reads them.
|
||||
|
||||
my $repo = File::Spec->rel2abs( File::Spec->catdir( $FindBin::Bin, '..', '..' ) );
|
||||
my $install = "$repo/xCAT-server/share/xcat/install/ubuntu";
|
||||
my $template = "$install/compute.subiquity.tmpl";
|
||||
plan skip_all => 'the Ubuntu install directory is not here' unless -d $install && -f $template;
|
||||
|
||||
sub packages_in {
|
||||
my ($path) = @_;
|
||||
return { map { $_ => 1 } grep { length } split /,/, xCAT::Postage::get_pkglist_tex($path) };
|
||||
}
|
||||
|
||||
sub resolved {
|
||||
my ( $os, $arch ) = @_;
|
||||
return xCAT::SvrUtils->get_pkglist_file_name( $install, 'compute', $os, $arch, $os =~ /^(ubuntu\d+\.\d+)/ ? $1 : $os );
|
||||
}
|
||||
|
||||
open( my $tfh, '<', $template ) or die "$template: $!";
|
||||
my $body = do { local $/; <$tfh> };
|
||||
close($tfh);
|
||||
my ($block) = $body =~ /^ packages:\n((?: - .*\n)+)/m;
|
||||
my %fixed = map { $_ => 1 } ( $block =~ /^ - ([^#\s]+)$/mg );
|
||||
ok( $fixed{chrony}, 'the Subiquity template names chrony among its fixed packages' );
|
||||
|
||||
my %time_daemon = map { $_ => 1 } qw(chrony ntp ntpsec);
|
||||
foreach my $case ( [ 'ubuntu20.04.6', 'x86_64' ], [ 'ubuntu20.04.6', 'ppc64le' ],
|
||||
[ 'ubuntu22.04.5', 'x86_64' ], [ 'ubuntu22.04.5', 'ppc64le' ],
|
||||
[ 'ubuntu24.04.4', 'x86_64' ], [ 'ubuntu24.04.4', 'ppc64le' ], [ 'ubuntu24.04.4', 'riscv64' ],
|
||||
[ 'ubuntu26.04.1', 'x86_64' ], [ 'ubuntu26.04.1', 'riscv64' ] ) {
|
||||
my ( $os, $arch ) = @$case;
|
||||
my $file = resolved( $os, $arch );
|
||||
ok( $file, "$os $arch resolves a compute pkglist" ) or next;
|
||||
my $packages = packages_in($file);
|
||||
ok( $packages->{chrony}, "$os $arch: the list names chrony, the daemon the template installs" );
|
||||
ok( !$packages->{ntp} && !$packages->{ntpdate}, "$os $arch: ... and not ntp, which would conflict with it" );
|
||||
my @daemons = grep { $time_daemon{$_} } keys %{ { %fixed, %$packages } };
|
||||
is( scalar(@daemons), 1, "$os $arch: the autoinstall transaction carries exactly one time daemon" );
|
||||
}
|
||||
|
||||
# The releases before Subiquity keep the shared list and its ntp.
|
||||
my $legacy = resolved( 'ubuntu16.04.7', 'x86_64' );
|
||||
is( $legacy, "$install/compute.pkglist", '16.04 still resolves the shared list' );
|
||||
ok( packages_in($legacy)->{ntp}, '... which keeps ntp for the releases where chrony was not the default' );
|
||||
|
||||
done_testing();
|
||||
@@ -16,6 +16,9 @@ like($tmpl, qr/autoinstall:/, 'template has autoinstall: key');
|
||||
like($tmpl, qr/version:\s*1/, 'template has version: 1');
|
||||
|
||||
like($tmpl, qr/^\s*identity:/m, 'template has an identity section so subiquity does not prompt');
|
||||
like($tmpl, qr/^ - #INCLUDE_DEFAULT_PKGLIST_AUTOINSTALL#\n/m, 'the packages list carries the osimage pkglist through the autoinstall token');
|
||||
like($tmpl, qr/^ - openssh-server\n/m, '... and keeps openssh-server, which xCAT needs on the node');
|
||||
like($tmpl, qr/^ - wget\n/m, '... and wget, which the early and late commands use');
|
||||
like($tmpl, qr/kernel:/, 'template has kernel section');
|
||||
like($tmpl, qr/package:\s*linux-generic/, 'template specifies linux-generic kernel');
|
||||
like($tmpl, qr/#UBUNTU_SUBIQUITY_APT_CONFIG#/, 'template renders apt section from osimage context');
|
||||
@@ -78,5 +81,39 @@ unlike($tmpl, qr/if \[ -x \/tmp\/pre\.sh \]/, 'pre.sh not checked with -x');
|
||||
# Subiquity behavior can be handled without cloning this template per release.
|
||||
unlike($tmpl, qr/noble-|jammy-|focal-/, 'template avoids release-specific apt suite names');
|
||||
like($tmpl, qr/#UBUNTU_SUBIQUITY_APT_CONFIG#/, 'template keeps dynamic apt renderer marker');
|
||||
like($tmpl, qr{2>&1'\n(?:\s*#[^\n]*\n)*\s*- rm -f (?:/target/etc/apt/sources\.list\.d/xcat-(?:otherpkgs|pkgdir)-\*\.(?:list|sources)\s+){4}/target/etc/apt/apt\.conf\.d/94curtin-config$}m,
|
||||
'the installer sources for the otherpkgs repository and the pkgdir mirrors, and the apt configuration curtin wrote, are removed from the target by a late-command of their own, after the post script block');
|
||||
|
||||
# the post script block: its status is the post script's, so a failed post script stops the install
|
||||
{
|
||||
my ($block) = $tmpl =~ /\n - '(\{\n.*?\n \} >>\/target\/var\/log\/xcat\/xcat\.log 2>&1)'\n/s;
|
||||
ok( defined $block, 'the post script block is found' ) or last;
|
||||
$block =~ s/''/'/g;
|
||||
require File::Temp;
|
||||
my $root = File::Temp->newdir();
|
||||
mkdir "$root/bin" or die;
|
||||
for my $tool ( 'curtin', 'wget' ) {
|
||||
open( my $fh, '>', "$root/bin/$tool" ) or die;
|
||||
print {$fh} $tool eq 'curtin' ? "#!/bin/sh\ncase \"\$*\" in *post.script*) exit 42;; esac\nexit 0\n" : "#!/bin/sh\nfor a; do case \"\$a\" in http*) touch \"\${a##*/}\";; esac; done\nexit 0\n";
|
||||
close $fh; chmod 0755, "$root/bin/$tool";
|
||||
}
|
||||
( my $script = $block ) =~ s{/target}{$root/target}g;
|
||||
$script =~ s{/tmp/pre-install\.log}{$root/pre-install.log}g;
|
||||
for my $token ( [ '#SUBIQUITYINSTALLNIC#', '' ], [ '#SUBIQUITYINSTALLMAC#', '52:54:00:00:00:01' ], [ '#HOSTNAME#', 'cn1' ], [ '#XCATVAR:XCATMASTER#', '192.0.2.10' ],
|
||||
[ '#COLONHTTPPORT#', '' ], [ '#TABLEBLANKOKAY:bootparams:$NODE:kcmdline#', '' ] ) {
|
||||
$script =~ s/\Q$token->[0]\E/$token->[1]/g;
|
||||
}
|
||||
require File::Path;
|
||||
File::Path::make_path( map { "$root/target/$_" } qw(etc/default root var/log/xcat) );
|
||||
open( my $hosts, '>', "$root/target/etc/hosts" ) or die; print {$hosts} "127.0.0.1 localhost\n"; close $hosts;
|
||||
open( my $pre, '>', "$root/pre-install.log" ) or die; close $pre;
|
||||
my $cwd = File::Spec->rel2abs('.');
|
||||
chdir $root or die;
|
||||
local $ENV{PATH} = "$root/bin:$ENV{PATH}";
|
||||
system( 'sh', '-c', $script );
|
||||
my $status = $? >> 8;
|
||||
chdir $cwd or die;
|
||||
is( $status, 42, 'a failing post script fails the late-command block, so Subiquity stops the install instead of switching the node to disk boot' );
|
||||
}
|
||||
|
||||
done_testing();
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Spec;
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# A riscv64 management node has no legacy Genesis: the image ships as the OpenEmbedded package
|
||||
# xcat-genesis-openembedded-riscv64, which mknb consumes. xCAT.spec already says so for the rpm
|
||||
# side and gives riscv64 its own dependency block.
|
||||
#
|
||||
# The deb side named xcat-genesis-scripts-amd64 in a plain Depends, and that package is
|
||||
# Architecture: all, so apt installed the x86 Genesis scripts (and, through them, the x86 Genesis
|
||||
# base) on a riscv64 management node. Restrict the dependency to the architectures that have a
|
||||
# legacy Genesis, and leave amd64 and ppc64el untouched.
|
||||
|
||||
my $repo_root = File::Spec->rel2abs(
|
||||
File::Spec->catdir( $FindBin::Bin, '..', '..' )
|
||||
);
|
||||
|
||||
sub read_file {
|
||||
my ($filename) = @_;
|
||||
open( my $fh, '<', $filename ) or die "Unable to read $filename: $!";
|
||||
my $content = do { local $/; <$fh> };
|
||||
close($fh);
|
||||
return $content;
|
||||
}
|
||||
|
||||
my $have_dpkg_deps = eval { require Dpkg::Deps; 1 } ? 1 : 0;
|
||||
|
||||
foreach my $pkg ( [ 'xCAT', 'xcat' ], [ 'xCATsn', 'xcatsn' ] ) {
|
||||
my ( $dir, $name ) = @$pkg;
|
||||
my $control = read_file( File::Spec->catfile( $repo_root, $dir, 'debian', 'control' ) );
|
||||
my ($depends) = $control =~ /^Depends:\s*(.*)$/m;
|
||||
ok( defined $depends, "$name debian/control has a Depends line" );
|
||||
my ($recommends) = $control =~ /^Recommends:\s*(.*)$/m;
|
||||
ok( defined $recommends, "$name debian/control has a Recommends line" );
|
||||
|
||||
my ($entry) = grep { /xcat-genesis-scripts/ } split( /\s*,\s*/, $depends );
|
||||
ok( defined $entry, "$name depends on a legacy Genesis scripts package" );
|
||||
like( $entry, qr/\[!riscv64\]/,
|
||||
"$name excludes riscv64 from the legacy Genesis scripts dependency" );
|
||||
|
||||
SKIP: {
|
||||
skip( "Dpkg::Deps is not available", 7 ) unless $have_dpkg_deps;
|
||||
|
||||
# Dpkg::Deps cannot parse a substvar, which dpkg-gencontrol expands before it gets here.
|
||||
( my $parsable = $depends ) =~ s/\$\{[^}]*\}\s*,?\s*//g;
|
||||
my %reduced = map {
|
||||
my $d = Dpkg::Deps::deps_parse( $parsable, reduce_arch => 1, host_arch => $_ );
|
||||
$_ => ( defined $d ? $d->output() : '' )
|
||||
} qw(riscv64 amd64 ppc64el);
|
||||
|
||||
unlike( $reduced{riscv64}, qr/xcat-genesis-scripts/,
|
||||
"$name on riscv64 does not pull the legacy Genesis scripts" );
|
||||
like( $reduced{amd64}, qr/xcat-genesis-scripts-amd64/,
|
||||
"$name on amd64 still pulls them" );
|
||||
like( $reduced{ppc64el}, qr/xcat-genesis-scripts-amd64/,
|
||||
"$name on ppc64el still pulls them" );
|
||||
|
||||
# The restriction must not take anything else with it: every other dependency of the
|
||||
# amd64 package must survive on riscv64.
|
||||
my @lost = grep { $reduced{riscv64} !~ /\Q$_\E/ }
|
||||
grep { !/xcat-genesis-scripts/ }
|
||||
map { my $d = $_; $d =~ s/\s*\(.*//; $d =~ s/\s*\[.*//; $d }
|
||||
split( /\s*,\s*/, $reduced{amd64} );
|
||||
is_deeply( \@lost, [],
|
||||
"$name on riscv64 keeps every other dependency" )
|
||||
or diag( "dropped: @lost" );
|
||||
|
||||
# A management node of any architecture can provision nodes of another, so what it SERVES
|
||||
# to those nodes -- the x86 boot payload and the Genesis images of the other architectures
|
||||
# -- stays recommended everywhere. Only the legacy Genesis of this node is architecture
|
||||
# specific, and that one is a dependency, not a recommendation.
|
||||
my %reduced_recommends = map {
|
||||
my $d = Dpkg::Deps::deps_parse( $recommends, reduce_arch => 1, host_arch => $_ );
|
||||
$_ => ( defined $d ? $d->output() : '' )
|
||||
} qw(riscv64 amd64);
|
||||
|
||||
like( $reduced_recommends{riscv64}, qr/\bsyslinux-xcat\b/,
|
||||
"$name on riscv64 still recommends the x86 boot payload it serves" );
|
||||
like( $reduced_recommends{riscv64}, qr/xcat-genesis-openembedded-x86-64/,
|
||||
"$name on riscv64 still recommends the Genesis image of the other architectures" );
|
||||
like( $reduced_recommends{amd64}, qr/\bsyslinux-xcat\b/,
|
||||
"$name on amd64 is unchanged" );
|
||||
}
|
||||
}
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Spec;
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# copycd builds the riscv64 boot loader by running grub-mkimage, and the plugin that runs it ships
|
||||
# in xcat-server. Nothing declared the package that provides that command, so a management or
|
||||
# service node installed without it copies riscv64 media and then produces no loader at all, while
|
||||
# DHCP keeps pointing every riscv64 node at the path where the loader should be.
|
||||
#
|
||||
# grub-common provides /usr/bin/grub-mkimage on every supported Ubuntu release. That the plugin
|
||||
# runs grub-mkimage is shown by ubuntu_copycd_grub2_loader.t, which drives it through a stub.
|
||||
|
||||
my $repo_root = File::Spec->rel2abs( File::Spec->catdir( $FindBin::Bin, '..', '..' ) );
|
||||
|
||||
sub depends_of {
|
||||
my ($package) = @_;
|
||||
my $file = File::Spec->catfile( $repo_root, $package, 'debian', 'control' );
|
||||
open( my $fh, '<', $file ) or die "Unable to read $file: $!";
|
||||
my $control = do { local $/; <$fh> };
|
||||
close($fh);
|
||||
my ($depends) = $control =~ /^Depends:\s*(.*)$/m;
|
||||
return [ split( /\s*,\s*/, $depends // '' ) ];
|
||||
}
|
||||
|
||||
my $server = depends_of('xCAT-server');
|
||||
ok( scalar( grep { /^grub-common\b/ } @{$server} ),
|
||||
'xcat-server depends on the package that provides grub-mkimage' );
|
||||
|
||||
# The plugin runs there, so the metapackages inherit it and must not carry their own copy.
|
||||
foreach my $package (qw(xCAT xCATsn)) {
|
||||
is_deeply( [ grep { /^grub-common\b/ } @{ depends_of($package) } ], [],
|
||||
"$package leaves the dependency with the package that runs the command" );
|
||||
}
|
||||
|
||||
done_testing();
|
||||
+3
-3
@@ -8,9 +8,9 @@ Vcs-browser: https://github.com/xcat2/xcat-core.git
|
||||
Homepage: https://xcat.org/
|
||||
|
||||
Package: xcat
|
||||
Architecture: amd64 ppc64el
|
||||
Depends: ${perl:Depends}, goconserver(>= 0.3.3-snap000000000000), xcat-server (>= 2.13-snap000000000000), xcat-client (>= 2.13-snap000000000000), libdbd-sqlite3-perl, isc-dhcp-server | kea, bind9, apache2, nfs-kernel-server, libxml-parser-perl, rsync, tftpd-hpa, libnet-telnet-perl, chrony | ntp, xcat-genesis-scripts-amd64 (>= 2.13-snap000000000000)
|
||||
Recommends: net-tools, nmap, kea, tftp-hpa, ipmitool-xcat (>= 1.8.17-1), syslinux[any-amd64], libsys-virt-perl, syslinux-xcat, xnba-undi, elilo-xcat, util-linux-extra, xcat-buildkit (>= 2.13-snap000000000000), xcat-probe (>= 2.13-snap000000000000), xcat-genesis-openembedded-x86-64, xcat-genesis-openembedded-ppc64le, xcat-genesis-openembedded-riscv64, xcat-genesis-openembedded-s390x
|
||||
Architecture: amd64 ppc64el riscv64
|
||||
Depends: ${perl:Depends}, goconserver(>= 0.3.3-snap000000000000), xcat-server (>= 2.13-snap000000000000), xcat-client (>= 2.13-snap000000000000), libdbd-sqlite3-perl, isc-dhcp-server | kea, bind9, apache2, nfs-kernel-server, libxml-parser-perl, rsync, tftpd-hpa, libnet-telnet-perl, chrony | ntp, nmap, ipmitool-xcat (>= 1.8.18-4), xcat-genesis-scripts-amd64 (>= 2.13-snap000000000000) [!riscv64]
|
||||
Recommends: net-tools, kea, tftp-hpa, syslinux[any-amd64], libsys-virt-perl, syslinux-xcat, xnba-undi, elilo-xcat, util-linux-extra, xcat-buildkit (>= 2.13-snap000000000000), xcat-probe (>= 2.13-snap000000000000), xcat-genesis-openembedded-x86-64, xcat-genesis-openembedded-ppc64le, xcat-genesis-openembedded-riscv64, xcat-genesis-openembedded-s390x
|
||||
Suggests: yaboot-xcat
|
||||
Description: Metapackage for a common, default xCAT setup
|
||||
xCAT is Extreme Cluster/Cloud Administration Toolkit. xCAT offers complete
|
||||
|
||||
@@ -829,7 +829,7 @@ elif ( pmatch "$OSVER" "ubuntu*" ); then
|
||||
|
||||
# upgrade existing packages
|
||||
apt-get -y update
|
||||
command="DEBIAN_FRONTEND=noninteractive apt-get -y --allow-unauthenticated --force-yes -o Dpkg::Options::='--force-confold' -o Dpkg::Options::='--force-confdef' upgrade "
|
||||
command="xcat_apt_get -o Dpkg::Options::='--force-confold' -o Dpkg::Options::='--force-confdef' upgrade"
|
||||
echo "=== $command"
|
||||
eval $command
|
||||
R=$?
|
||||
@@ -841,7 +841,7 @@ elif ( pmatch "$OSVER" "ubuntu*" ); then
|
||||
# but keeping this here doesn't really hurt. Anything that looks like a group will
|
||||
# be installed as though it were a package.)
|
||||
if [ -n "$groups" ]; then
|
||||
command="$ENVLIST apt-get -q -y --force-yes install --no-install-recommends $groups"
|
||||
command="$ENVLIST xcat_apt_get -q install --no-install-recommends $groups"
|
||||
echo "=== $command"
|
||||
eval $command
|
||||
R=$?
|
||||
@@ -855,7 +855,7 @@ elif ( pmatch "$OSVER" "ubuntu*" ); then
|
||||
|
||||
# install packages
|
||||
if [ -n "$pkgs" ]; then
|
||||
command="$ENVLIST apt-get -q -y --force-yes install --no-install-recommends $pkgs"
|
||||
command="$ENVLIST xcat_apt_get -q install --no-install-recommends $pkgs"
|
||||
echo "=== $command"
|
||||
eval $command
|
||||
R=$?
|
||||
@@ -865,16 +865,16 @@ elif ( pmatch "$OSVER" "ubuntu*" ); then
|
||||
fi
|
||||
if [ -n "$cudapkgs" ]; then
|
||||
|
||||
command="$ENVLIST apt-get -q -y --force-yes install --no-install-recommends $cudapkgs"
|
||||
command="$ENVLIST xcat_apt_get -q install --no-install-recommends $cudapkgs"
|
||||
echo "=== $command"
|
||||
# the nvidia-346 postinstall script will trigger the building of nvidia driver, it will use the ARCH environment parameter, unset the ARCH env variable will resolve this issue
|
||||
original_arch=$ARCH
|
||||
unset ARCH
|
||||
eval $command
|
||||
R=$?
|
||||
# re declare the ARCH env after installing cuda command done
|
||||
ARCH=$original_arch
|
||||
export ARCH
|
||||
R=$?
|
||||
if [ $R -ne 0 ]; then
|
||||
RETURNVAL=$R
|
||||
fi
|
||||
@@ -997,10 +997,10 @@ else
|
||||
original_arch=$ARCH
|
||||
unset ARCH
|
||||
result=`eval $cmd 2>&1`
|
||||
R=$?
|
||||
# re declare the ARCH env after installing cuda command done
|
||||
ARCH=$original_arch
|
||||
export ARCH
|
||||
R=$?
|
||||
if [ $R -ne 0 ]; then
|
||||
RETURNVAL=$R
|
||||
logger -t $log_label -p local4.info "ospkgs: $cmd\n $result"
|
||||
|
||||
@@ -905,9 +905,9 @@ EOF`
|
||||
elif [ $hasapt -eq 1 ]; then
|
||||
apt_get_update_if_repos_changed $REPOFILE
|
||||
if [ $VERBOSE ]; then
|
||||
echo "$envlist DEBIAN_FRONTEND=noninteractive apt-get -y --allow-unauthenticated --force-yes -o Dpkg::Options::='--force-confold' -o Dpkg::Options::='--force-confdef' upgrade"
|
||||
echo "$envlist xcat_apt_get -o Dpkg::Options::='--force-confold' -o Dpkg::Options::='--force-confdef' upgrade"
|
||||
fi
|
||||
result=`eval $envlist DEBIAN_FRONTEND=noninteractive apt-get -y --allow-unauthenticated --force-yes -o Dpkg::Options::='--force-confold' -o Dpkg::Options::='--force-confdef' upgrade 2>&1`
|
||||
result=`eval $envlist xcat_apt_get -o Dpkg::Options::='--force-confold' -o Dpkg::Options::='--force-confdef' upgrade 2>&1`
|
||||
R=$?
|
||||
if [ $R -ne 0 ]; then
|
||||
RETURNVAL=$R
|
||||
@@ -1022,9 +1022,9 @@ EOF`
|
||||
elif [ $hasapt -eq 1 ]; then
|
||||
apt_get_update_if_repos_changed $REPOFILE
|
||||
if [ $VERBOSE ]; then
|
||||
echo "$envlist DEBIAN_FRONTEND=noninteractive apt-get -q -y --force-yes -o Dpkg::Options::='--force-confold' -o Dpkg::Options::='--force-confdef' install $repo_pkgs"
|
||||
echo "$envlist xcat_apt_get -q -o Dpkg::Options::='--force-confold' -o Dpkg::Options::='--force-confdef' install $repo_pkgs"
|
||||
fi
|
||||
result=`eval $envlist DEBIAN_FRONTEND=noninteractive apt-get -q -y --force-yes -o Dpkg::Options::='--force-confold' -o Dpkg::Options::='--force-confdef' install $repo_pkgs 2>&1`
|
||||
result=`eval $envlist xcat_apt_get -q -o Dpkg::Options::='--force-confold' -o Dpkg::Options::='--force-confdef' install $repo_pkgs 2>&1`
|
||||
R=$?
|
||||
if [ $R -ne 0 ]; then
|
||||
RETURNVAL=$R
|
||||
|
||||
@@ -631,20 +631,6 @@ fi
|
||||
#if the service restart with "service/systemctl" failed
|
||||
#try to kill the process and start
|
||||
if [ "$?" != "0" ];then
|
||||
PIDLIST=`ps aux | grep -v grep | grep "/usr/sbin/sshd"|awk -F" " '{print $2}'|xargs`
|
||||
if [ -n "$PIDLIST" ]; then
|
||||
kill -9 $PIDLIST
|
||||
waited=0
|
||||
while [ $waited -lt 10 ]; do
|
||||
alive=0
|
||||
for pid in $PIDLIST; do
|
||||
if kill -0 $pid 2>/dev/null; then alive=1; fi
|
||||
done
|
||||
if [ $alive -eq 0 ]; then break; fi
|
||||
sleep 1
|
||||
waited=`expr $waited + 1`
|
||||
done
|
||||
fi
|
||||
/usr/sbin/sshd
|
||||
xcat_restart_sshd_after_failed_service_restart
|
||||
fi
|
||||
kill -9 $CREDPID
|
||||
|
||||
@@ -19,26 +19,34 @@
|
||||
#
|
||||
#####################################################
|
||||
|
||||
[ -f "/xcatpost/xcatlib.sh" ] && . /xcatpost/xcatlib.sh
|
||||
if [ "$XCATDSKLSPOST_SOURCE_ONLY" != "1" ]; then
|
||||
if [ -f "/xcatpost/xcatlib.sh" ]; then
|
||||
. /xcatpost/xcatlib.sh
|
||||
else
|
||||
str_dir_name=`dirname $0`
|
||||
[ -f "$str_dir_name/xcatlib.sh" ] && . "$str_dir_name/xcatlib.sh"
|
||||
fi
|
||||
|
||||
if [ -f /xcatpost/mypostscript.post ]; then
|
||||
XCATDEBUGMODE=`grep 'XCATDEBUGMODE=' /xcatpost/mypostscript.post | cut -d= -f2 | tr -d \'\" | tr A-Z a-z`
|
||||
MASTER_IP=`grep '^MASTER_IP=' /xcatpost/mypostscript.post |cut -d= -f2|sed s/\'//g`
|
||||
NODE=`grep '^NODE=' /xcatpost/mypostscript.post |cut -d= -f2|sed s/\'//g`
|
||||
else
|
||||
for param in `cat /proc/cmdline`; do
|
||||
key=`echo $param|awk -F= '{print $1}'`
|
||||
if [ "$key" = "xcatdebugmode" ]; then
|
||||
XCATDEBUGMODE=`echo $param|awk -F= '{print $2}'| tr -d \'\" | tr A-Z a-z`
|
||||
fi
|
||||
if [ -f /xcatpost/mypostscript.post ]; then
|
||||
XCATDEBUGMODE=`grep 'XCATDEBUGMODE=' /xcatpost/mypostscript.post | cut -d= -f2 | tr -d \'\" | tr A-Z a-z`
|
||||
MASTER_IP=`grep '^MASTER_IP=' /xcatpost/mypostscript.post |cut -d= -f2|sed s/\'//g`
|
||||
NODE=`grep '^NODE=' /xcatpost/mypostscript.post |cut -d= -f2|sed s/\'//g`
|
||||
else
|
||||
for param in `cat /proc/cmdline`; do
|
||||
key=`echo $param|awk -F= '{print $1}'`
|
||||
if [ "$key" = "xcatdebugmode" ]; then
|
||||
XCATDEBUGMODE=`echo $param|awk -F= '{print $2}'| tr -d \'\" | tr A-Z a-z`
|
||||
fi
|
||||
|
||||
if [ "$key" = "LOGSERVER" ]; then
|
||||
MASTER_IP=`echo $param|awk -F= '{print $2}'`
|
||||
fi
|
||||
done
|
||||
if [ "$key" = "LOGSERVER" ]; then
|
||||
MASTER_IP=`echo $param|awk -F= '{print $2}'`
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
XCATINFOFILE=/opt/xcat/xcatinfo
|
||||
XCAT_WGET_LOG=${XCAT_WGET_LOG:-/tmp/wget.log}
|
||||
|
||||
#echolog: process message log and echo in xcatdsklspost
|
||||
#arguments:
|
||||
@@ -83,6 +91,45 @@ echolog()
|
||||
}
|
||||
|
||||
|
||||
download_mypostscript()
|
||||
{
|
||||
server=$1
|
||||
node=$2
|
||||
max_retries=$3
|
||||
TFTPDIR=$4
|
||||
if [ -z $server ]; then
|
||||
return 1;
|
||||
fi
|
||||
if [ -z "$TFTPDIR" ]; then
|
||||
TFTPDIR="/tftpboot"
|
||||
fi
|
||||
retry=0
|
||||
rc=1
|
||||
|
||||
|
||||
echolog "debug" "trying to download http://$server$TFTPDIR/mypostscripts/mypostscript.$node..."
|
||||
while [ 0 -eq 0 ]; do
|
||||
wget -N --waitretry=10 --random-wait -T 60 http://$server$TFTPDIR/mypostscripts/mypostscript.$node -P /$xcatpost 2>> "$XCAT_WGET_LOG"
|
||||
rc=$?
|
||||
# if no error and the file was downloaded
|
||||
if [ $rc -eq 0 ] && [ -f /$xcatpost/mypostscript.$node ]; then
|
||||
mv /$xcatpost/mypostscript.$node /$xcatpost/mypostscript
|
||||
echolog "debug" "mypostscript.$node is downloaded successfully."
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
retry=$(($retry+1))
|
||||
if [ $retry -eq $max_retries ]; then
|
||||
echolog "debug" "http://$server$TFTPDIR/mypostscripts/mypostscript.$node is not available."
|
||||
break
|
||||
fi
|
||||
|
||||
done
|
||||
return $rc
|
||||
}
|
||||
|
||||
|
||||
download_postscripts()
|
||||
{
|
||||
server=$1
|
||||
@@ -111,16 +158,16 @@ download_postscripts()
|
||||
# These dispatcher scripts are not needed by the legacy netboot post
|
||||
# path. Newer wget parses HTML-looking regex strings inside downloaded
|
||||
# scripts and fails the whole recursive download on bogus URLs.
|
||||
export LANG=C; wget -l inf -nH -N -r --waitretry=10 --random-wait -e robots=off -T 60 -nH --cut-dirs=2 --reject "index.html*,post.xcat.ng,post.xcat.rhels10" --no-parent http://$server$INSTALLDIR/postscripts/ -P /$xcatpost 2> /tmp/wget.log
|
||||
export LANG=C; wget -l inf -nH -N -r --waitretry=10 --random-wait -e robots=off -T 60 -nH --cut-dirs=2 --reject "index.html*,post.xcat.ng,post.xcat.rhels10" --no-parent http://$server$INSTALLDIR/postscripts/ -P /$xcatpost 2> "$XCAT_WGET_LOG"
|
||||
rc=$?
|
||||
if [ $rc -eq 0 ]; then
|
||||
# return from wget was 0 but some OS do not return errors, so we
|
||||
# have additional checks for
|
||||
# failed: Connection httpd not running
|
||||
# 404: Not Found - if directory does not exist
|
||||
grep -i -E "... failed: Connection refused.$" /tmp/wget.log
|
||||
grep -i -E "... failed: Connection refused.$" "$XCAT_WGET_LOG"
|
||||
rc1=$?
|
||||
grep -i -E "ERROR 404: Not Found.$" /tmp/wget.log
|
||||
grep -i -E "ERROR 404: Not Found.$" "$XCAT_WGET_LOG"
|
||||
rc2=$?
|
||||
# check to see no errors at all, grep returns 1
|
||||
if [ $rc1 -eq 1 ] && [ $rc2 -eq 1 ]; then
|
||||
@@ -143,45 +190,6 @@ download_postscripts()
|
||||
}
|
||||
|
||||
|
||||
download_mypostscript()
|
||||
{
|
||||
server=$1
|
||||
node=$2
|
||||
max_retries=$3
|
||||
TFTPDIR=$4
|
||||
if [ -z $server ]; then
|
||||
return 1;
|
||||
fi
|
||||
if [ -z "$TFTPDIR" ]; then
|
||||
TFTPDIR="/tftpboot"
|
||||
fi
|
||||
retry=0
|
||||
rc=1
|
||||
|
||||
|
||||
echolog "debug" "trying to download http://$server$TFTPDIR/mypostscripts/mypostscript.$node..."
|
||||
while [ 0 -eq 0 ]; do
|
||||
wget -N --waitretry=10 --random-wait -T 60 http://$server$TFTPDIR/mypostscripts/mypostscript.$node -P /$xcatpost 2>> /tmp/wget.log
|
||||
rc=$?
|
||||
# if no error and the file was downloaded
|
||||
if [ $rc -eq 0 ] && [ -f /$xcatpost/mypostscript.$node ]; then
|
||||
mv /$xcatpost/mypostscript.$node /$xcatpost/mypostscript
|
||||
echolog "debug" "mypostscript.$node is downloaded successfully."
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
retry=$(($retry+1))
|
||||
if [ $retry -eq $max_retries ]; then
|
||||
echolog "debug" "http://$server$TFTPDIR/mypostscripts/mypostscript.$node is not available."
|
||||
break
|
||||
fi
|
||||
|
||||
done
|
||||
return $rc
|
||||
}
|
||||
|
||||
|
||||
# pmatch determines if 1st argument string is matched by 2nd argument pattern
|
||||
|
||||
pmatch ()
|
||||
@@ -216,6 +224,10 @@ parsehttpserver ()
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$XCATDSKLSPOST_SOURCE_ONLY" = "1" ]; then
|
||||
return 0 2>/dev/null || exit 0
|
||||
fi
|
||||
|
||||
# Main
|
||||
# parse the arguments
|
||||
log_label="xcat.updatenode"
|
||||
|
||||
@@ -833,6 +833,42 @@ function msgutil {
|
||||
msgutil_r "" "$@"
|
||||
}
|
||||
|
||||
function xcat_wait_for_processes_to_exit {
|
||||
local pidlist="$1"
|
||||
local max_wait="${2:-10}"
|
||||
local waited=0
|
||||
local alive
|
||||
local pid
|
||||
|
||||
while [ $waited -lt $max_wait ]; do
|
||||
alive=0
|
||||
for pid in $pidlist; do
|
||||
if kill -0 $pid 2>/dev/null; then
|
||||
alive=1
|
||||
fi
|
||||
done
|
||||
if [ $alive -eq 0 ]; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
waited=`expr $waited + 1`
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
function xcat_restart_sshd_after_failed_service_restart {
|
||||
local sshd_cmd="${1:-/usr/sbin/sshd}"
|
||||
local PIDLIST
|
||||
|
||||
PIDLIST=`ps aux | grep -v grep | grep "/usr/sbin/sshd"|awk -F" " '{print $2}'|xargs`
|
||||
if [ -n "$PIDLIST" ]; then
|
||||
kill -9 $PIDLIST
|
||||
xcat_wait_for_processes_to_exit "$PIDLIST"
|
||||
fi
|
||||
$sshd_cmd
|
||||
}
|
||||
|
||||
function fetch_mypostscript {
|
||||
local postroot
|
||||
postroot=$1
|
||||
|
||||
@@ -14,6 +14,12 @@ xcat_find_rpm_package_manager()
|
||||
fi
|
||||
}
|
||||
|
||||
# The xCAT repositories are unsigned, and an unattended node cannot answer a debconf prompt.
|
||||
xcat_apt_get()
|
||||
{
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -y --allow-unauthenticated "$@"
|
||||
}
|
||||
|
||||
# Keep the marker assignment last so callers know the whole library loaded.
|
||||
|
||||
xcat_is_el_modular_pkgdir()
|
||||
|
||||
@@ -7,9 +7,9 @@ Standards-Version: 3.9.4
|
||||
Homepage: https://xcat.org/
|
||||
|
||||
Package: xcatsn
|
||||
Architecture: amd64 ppc64el
|
||||
Depends: ${perl:Depends}, goconserver (>=0.3.3-snap000000000000), xcat-server (>= 2.13-snap000000000000), xcat-client (>= 2.13-snap000000000000), libdbd-sqlite3-perl, libxml-parser-perl, tftpd-hpa, libnet-telnet-perl, isc-dhcp-server | kea, bind9, apache2, nfs-kernel-server, xcat-genesis-scripts-amd64 (>= 2.13-snap000000000000)
|
||||
Recommends: net-tools, nmap, kea, tftp-hpa, ipmitool-xcat (>= 1.8.17-1), syslinux[any-amd64], libsys-virt-perl, syslinux-xcat, xnba-undi, elilo-xcat, xcat-buildkit (>= 2.13-snap000000000000), xcat-probe (>= 2.13-snap000000000000), xcat-genesis-openembedded-x86-64, xcat-genesis-openembedded-ppc64le, xcat-genesis-openembedded-riscv64, xcat-genesis-openembedded-s390x
|
||||
Architecture: amd64 ppc64el riscv64
|
||||
Depends: ${perl:Depends}, goconserver (>=0.3.3-snap000000000000), xcat-server (>= 2.13-snap000000000000), xcat-client (>= 2.13-snap000000000000), libdbd-sqlite3-perl, libxml-parser-perl, tftpd-hpa, libnet-telnet-perl, isc-dhcp-server | kea, bind9, apache2, nfs-kernel-server, nmap, ipmitool-xcat (>= 1.8.18-4), xcat-genesis-scripts-amd64 (>= 2.13-snap000000000000) [!riscv64]
|
||||
Recommends: net-tools, kea, tftp-hpa, syslinux[any-amd64], libsys-virt-perl, syslinux-xcat, xnba-undi, elilo-xcat, xcat-buildkit (>= 2.13-snap000000000000), xcat-probe (>= 2.13-snap000000000000), xcat-genesis-openembedded-x86-64, xcat-genesis-openembedded-ppc64le, xcat-genesis-openembedded-riscv64, xcat-genesis-openembedded-s390x
|
||||
Suggests: yaboot-xcat
|
||||
Description: Metapackage for a common, default xCAT service node setup
|
||||
xCATsn is a service node management package intended for at-scale
|
||||
|
||||
Reference in New Issue
Block a user