diff --git a/build-utils/lib/XCAT/BuildUtils.pm b/build-utils/lib/XCAT/BuildUtils.pm new file mode 100644 index 000000000..60ecf0877 --- /dev/null +++ b/build-utils/lib/XCAT/BuildUtils.pm @@ -0,0 +1,27 @@ +package XCAT::BuildUtils; + +use strict; +use warnings; + +use Exporter qw(import); + +our @EXPORT_OK = qw(targetarch_from_target); + +sub targetarch_from_target { + my ( $target, $default_arch ) = @_; + return $default_arch unless defined($target) && length($target); + + my @parts = map { + my $part = lc($_); + $part =~ s/^\s+|\s+$//g; + $part; + } split /-/, $target; + + for my $part (reverse @parts) { + return $part + if $part =~ /^(?:x86_64|i[3-6]86|ppc64le|ppc64|aarch64|riscv64|s390x|armv7hl)$/; + } + return $parts[-1]; +} + +1; diff --git a/build-utils/rpm-architectures.sh b/build-utils/rpm-architectures.sh new file mode 100644 index 000000000..80c215796 --- /dev/null +++ b/build-utils/rpm-architectures.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +# Architecture sets shared by the RPM build entry points and their tests. +XCAT_CORE_RPM_ARCHES="x86_64 ppc64 ppc64le s390x aarch64 riscv64" +XCAT_LOCAL_RPM_ARCHES="x86_64 ppc64 s390x aarch64 riscv64" +XCAT_LOCAL_COLLECT_ARCHES="noarch x86_64 ppc64 riscv64" diff --git a/buildcore.sh b/buildcore.sh index 192ddf101..fb0ebe503 100755 --- a/buildcore.sh +++ b/buildcore.sh @@ -37,6 +37,7 @@ SCRIPT=$(readlink -f $0) SCRIPTPATH=`dirname $SCRIPT` +. "$SCRIPTPATH/build-utils/rpm-architectures.sh" . "$SCRIPTPATH/build-utils/source-only.sh" . "$SCRIPTPATH/build-utils/buildcore-source-only.sh" @@ -392,7 +393,7 @@ for rpmname in xCAT xCATsn; do ./makerpm $rpmname "$EMBED" if [ $? -ne 0 ]; then FAILEDRPMS="$FAILEDRPMS $rpmname"; fi else - for arch in $(xcat_rpm_build_arches x86_64 ppc64 ppc64le s390x aarch64); do + for arch in $(xcat_rpm_build_arches $XCAT_CORE_RPM_ARCHES); do if [ "$rpmname" = "xCAT-OpenStack" -a "$arch" != "x86_64" ] || [ "$rpmname" = "xCAT-OpenStack-baremetal" -a "$arch" != "x86_64" ] ; then continue; fi # only bld openstack for x86_64 for now ./makerpm $rpmname $arch "$EMBED" if [ $? -ne 0 ]; then FAILEDRPMS="$FAILEDRPMS $rpmname-$arch"; fi diff --git a/buildlocal.sh b/buildlocal.sh index 853471758..f8e94e8a5 100755 --- a/buildlocal.sh +++ b/buildlocal.sh @@ -20,6 +20,8 @@ if [ -z "$CURDIR" ]; then CURDIR=$(pwd) fi +. "$CURDIR/build-utils/rpm-architectures.sh" + echo "CURDIR is $CURDIR" echo "OSNAME is $OSNAME!" echo "NAMEALL is $NAMEALL" @@ -87,13 +89,13 @@ else echo "This is an $OSNAME system" if [ "$OS" = "SUSE" ]; then - rm -rf /usr/src/packages/RPMS/noarch/* - rm -rf /usr/src/packages/RPMS/x86_64/* - rm -rf /usr/src/packages/RPMS/ppc64/* + for arch in $XCAT_LOCAL_COLLECT_ARCHES; do + rm -rf /usr/src/packages/RPMS/$arch/* + done else - rm -rf /root/rpmbuild/RPMS/noarch/* - rm -rf /root/rpmbuild/RPMS/x86_64/* - rm -rf /root/rpmbuild/RPMS/ppc64/* + for arch in $XCAT_LOCAL_COLLECT_ARCHES; do + rm -rf /root/rpmbuild/RPMS/$arch/* + done fi mkdir -p $CURDIR/build/ @@ -123,7 +125,7 @@ echo "This is an $OSNAME system" $CURDIR/makerpm $rpmname if [ $? -ne 0 ]; then FAILEDRPMS="$FAILEDRPMS $rpmname"; fi else - for arch in x86_64 ppc64 s390x aarch64; do + for arch in $XCAT_LOCAL_RPM_ARCHES; do $CURDIR/makerpm $rpmname $arch if [ $? -ne 0 ]; then FAILEDRPMS="$FAILEDRPMS $rpmname-$arch"; fi done @@ -131,13 +133,13 @@ echo "This is an $OSNAME system" done if [ "$OS" = "SUSE" ]; then - cp /usr/src/packages/RPMS/noarch/* $CURDIR/build/ - cp /usr/src/packages/RPMS/x86_64/* $CURDIR/build/ - cp /usr/src/packages/RPMS/ppc64/* $CURDIR/build/ + for arch in $XCAT_LOCAL_COLLECT_ARCHES; do + cp /usr/src/packages/RPMS/$arch/* $CURDIR/build/ + done else - cp /root/rpmbuild/RPMS/noarch/* $CURDIR/build/ - cp /root/rpmbuild/RPMS/x86_64/* $CURDIR/build/ - cp /root/rpmbuild/RPMS/ppc64/* $CURDIR/build/ + for arch in $XCAT_LOCAL_COLLECT_ARCHES; do + cp /root/rpmbuild/RPMS/$arch/* $CURDIR/build/ + done fi #begin to create repo for redhat platform @@ -161,4 +163,3 @@ EOF fi - diff --git a/buildrpms.pl b/buildrpms.pl index fee5ada80..72f95ba97 100755 --- a/buildrpms.pl +++ b/buildrpms.pl @@ -51,6 +51,8 @@ use Pod::Usage qw(pod2usage); use autodie; use autodie qw(cp); +require "$Bin/build-utils/lib/XCAT/BuildUtils.pm"; + my $SOURCES = "$ENV{HOME}/rpmbuild/SOURCES"; # Ensure the rpmbuild tree exists. buildrpms stages source tarballs into $SOURCES, but it only # runs rpmdev-setuptree in the one-time env-setup path -- so on a host where that never ran (or @@ -294,15 +296,6 @@ sub genesis_tarch_from_targetarch { return $targetarch; } -sub targetarch_from_target { - my ($target) = @_; - return $ARCH unless defined $target && length $target; - my @parts = split /-/, $target; - my $arch = $parts[-1]; - $arch =~ s/^\s+|\s+$//g; - return lc $arch; -} - # product(\@A, \@B) returns the catersian product of \@A and \@B sub product { my ($a, $b) = @_; @@ -462,7 +455,8 @@ sub buildspkgs { my $ext = $opts{mock_uniqueext} ? "-$opts{mock_uniqueext}" : ""; my $chroot = "$pkg-$target$ext"; - my $targetarch = targetarch_from_target($target); + my $targetarch = + XCAT::BuildUtils::targetarch_from_target( $target, $ARCH ); my $genesis_tarch = genesis_tarch_from_targetarch($targetarch); my $diskcache = ( @@ -518,7 +512,8 @@ sub buildpkgs { my $chroot = "$pkg-$target$ext"; # get x86_64 from alma+epel-9-x86_64 - my $targetarch = targetarch_from_target($target); + my $targetarch = + XCAT::BuildUtils::targetarch_from_target( $target, $ARCH ); # xCAT genesis packages include the translated target arch in their file names. my $arch = is_in($pkg, @NATIVE_PACKAGES) ? $targetarch : "noarch"; @@ -1106,6 +1101,12 @@ once per target). When omitted, the default is a single C<< +epel-10-. +A riscv64 build on an x86_64 builder needs a mock configuration that sets +C (with the riscv64 qemu-user-static +emulator registered through binfmt); the stock C only allows +riscv64 build hosts. Copy it under a new name in C, add the forcearch +option, and pass that name as C<--target>, e.g. C<--target=rocky-10-riscv64-xcat>. + =item B<--package>=I Build only selected package(s). Repeatable. @@ -1114,7 +1115,7 @@ Build only selected package(s). Repeatable. Build only the arch-native packages (C, C, C) -- the ones whose rpms carry the target arch. Everything else in the default set is C and -identical on every arch, so a secondary-arch builder (e.g. ppc64le) uses this to avoid +identical on every arch, so a secondary-arch builder (e.g. ppc64le or riscv64) uses this to avoid rebuilding the noarch packages that the x86_64 builder already produces. Ignored if C<--package> is given. diff --git a/docs/source/advanced/mixed_cluster/building_stateless_images.rst b/docs/source/advanced/mixed_cluster/building_stateless_images.rst index b08f855e0..c3a1ffa5a 100644 --- a/docs/source/advanced/mixed_cluster/building_stateless_images.rst +++ b/docs/source/advanced/mixed_cluster/building_stateless_images.rst @@ -9,15 +9,16 @@ 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* and aarch64 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. -Cross-build ppc64*/aarch64 stateless/statelite image on x86_64 management node ------------------------------------------------------------------------------- +Cross-build ppc64*/aarch64/riscv64 stateless/statelite image on x86_64 management node +-------------------------------------------------------------------------------------- -#. Download qemu-user-static binaries for ppc64le and/or aarch64: :: +#. Download qemu-user-static binaries for ppc64le, aarch64 and/or riscv64: :: wget https://github.com/multiarch/qemu-user-static/releases/latest/download/qemu-ppc64le-static -P /usr/bin wget https://github.com/multiarch/qemu-user-static/releases/latest/download/qemu-aarch64-static -P /usr/bin + wget https://github.com/multiarch/qemu-user-static/releases/latest/download/qemu-riscv64-static -P /usr/bin chmod 755 /usr/bin/qemu-*-static #. Configure systemd-binfmt accordingly: :: @@ -26,7 +27,7 @@ Cross-build ppc64*/aarch64 stateless/statelite image on x86_64 management node bash qemu-binfmt-conf.sh --systemd 'ALL' --qemu-path '/usr/bin' --qemu-suffix '-static' --persistent 'yes' systemctl restart systemd-binfmt.service -#. Make sure ``osimage.osarch`` of your image is set to your target architecture eg. ``ppc64le`` or ``aarch64``. +#. Make sure ``osimage.osarch`` of your image is set to your target architecture eg. ``ppc64le``, ``aarch64`` or ``riscv64``. Now, ``genimage`` will use systemd-binfmt and the qemu-user-static binary to build the image for the specified architecture. diff --git a/docs/source/advanced/mixed_cluster/support_matrix.rst b/docs/source/advanced/mixed_cluster/support_matrix.rst index 8fcbacfd0..8bf5e69e7 100644 --- a/docs/source/advanced/mixed_cluster/support_matrix.rst +++ b/docs/source/advanced/mixed_cluster/support_matrix.rst @@ -1,43 +1,43 @@ Support Matrix ============== -+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+ -| | RHEL | SLES | RHEL | SLES | Ubuntu | RHEL | SLES | Ubuntu | RHEL | SLES | Ubuntu | -| | ppc64 | ppc64 | x86_64 | x86_64 | x86_64 | ppc64le | ppc64le | ppc64el | aarch64 | aarch64 | aarch64 | -| | CN | CN | CN | CN | CN | CN | CN | CN | CN | CN | CN | -+=========+=========+=========+=========+=========+=========+=========+=========+=========+=========+=========+=========+ -| RHEL | | | | | | | | | | | | -| ppc64 | yes | yes | yes | yes | yes | yes | yes | yes | no | no | no | -| MN/SN | | | [1]_ | [1]_ | [1]_ | | | | | | | -+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+ -| SLES | | | | | | | | | | | | -| ppc64 | yes | yes | yes | yes | yes | yes | yes | yes | no | no | no | -| MN/SN | | | [1]_ | [1]_ | [1]_ | | | | | | | -+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+ -| RHEL | | | | | | | | | | | | -| x86_64 | yes | yes | yes | yes | yes | yes | yes | yes | yes | no | no | -| MN/SN | [4]_ | [4]_ | | | | | | | | | | -+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+ -| SLES | | | | | | | | | | | | -| x86_64 | yes | yes | yes | yes | yes | yes | yes | yes | yes | no | no | -| MN/SN | [4]_ | [4]_ | | | | | | | | | | -+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+ -| Ubuntu | | | | | | | | | | | | -| x86_64 | yes | yes | yes | yes | yes | yes | yes | yes | yes | no | no | -| MN/SN | [5]_ | [5]_ | | | | | | | | | | -+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+ -| RHEL | | | | | | | | | | | | -| ppc64le | yes | yes | yes | yes | yes | yes | yes | yes | no | no | no | -| MN/SN | [2]_ | [2]_ | | | | | | | | | | -+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+ -| SLES | | | | | | | | | | | | -| ppc64le | no | no | yes | yes | yes | yes | yes | yes | no | no | no | -| MN/SN | | | | | | | | | | | | -+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+ -| Ubuntu | | | | | | | | | | | | -| ppc64el | yes | yes | yes | yes | yes | yes | yes | yes | no | no | no | -| MN/SN | [3]_ | [3]_ | | | | | | | | | | -+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+ ++---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+ +| | 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]_ | | | | | | | | | | | ++---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+ Notes: @@ -53,3 +53,4 @@ Notes: .. [3] If the compute nodes are DFM managed systems, will need xCAT 2.10 or high versions and the ppc64le DFM and ppc64le hardware server on the management node. .. [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`. diff --git a/docs/source/developers/guides/code/builds.rst b/docs/source/developers/guides/code/builds.rst index 9f7f5cbe3..6e016881c 100644 --- a/docs/source/developers/guides/code/builds.rst +++ b/docs/source/developers/guides/code/builds.rst @@ -20,6 +20,12 @@ binary rpms for each architecture. ``rpmbuild`` does not need the packages named in ``BuildRequires`` to make a source rpm, so this also builds on a machine that cannot complete a full build. ``SRCONLY`` is not supported on AIX. +``buildcore.sh`` builds the architecture specific packages (``xCAT``, ``xCATsn``, +``xCAT-genesis-scripts``) for every supported architecture, riscv64 included, with +``rpmbuild --target``. The build host therefore needs an rpm that knows the riscv64 +architecture and the ``Recommends:`` tag (EL8 or later); an older host fails the riscv64 +builds and, as the script only publishes when every architecture built, produces no rpms. + xcat-deps --------- diff --git a/docs/source/developers/guides/code/dhcp_backend_validation_matrix.rst b/docs/source/developers/guides/code/dhcp_backend_validation_matrix.rst index 8bb769b15..dbcf15339 100644 --- a/docs/source/developers/guides/code/dhcp_backend_validation_matrix.rst +++ b/docs/source/developers/guides/code/dhcp_backend_validation_matrix.rst @@ -226,6 +226,31 @@ paths. - package installation on ``ppc64el``; DHCP offer; boot file handoff; POWER boot-path correctness; Genesis shell when live validation is in scope + * - EL10 + - ``riscv64`` + - ``Kea`` + - ``UEFI grub2 handoff`` + - DHCP offer carries ``boot/grub2/grub2.riscv64`` for client + architecture 27 (``0x001b``); ``grub2.riscv64`` fetches + ``boot/grub2/grub.cfg-`` for discovery and the + per-node ``grub.cfg-`` after ``nodeset``; Genesis shell when + Genesis payload validation is in scope + * - EL10 + - ``riscv64`` + - ``ISC`` + - ``UEFI grub2 handoff`` + - same as the Kea row through the ISC subnet branch for client + architecture ``00:1b``; the riscv64 branch must answer before the + ``/yaboot`` fallback + * - EL10 + - ``riscv64`` + - ``Kea``, ``ISC`` + - ``UEFI HTTP boot`` + - firmware configured for HTTP boot sends client architecture 28 + (``0x001c``) and only accepts an offer whose boot file is a URL and + whose reply carries the ``HTTPClient`` vendor class; the class is per + subnet, so the URL must name the management node address of that + network and honour ``site.httpport`` Current Lab Baseline -------------------- diff --git a/docs/source/guides/admin-guides/basic_concepts/xcat_object/node.rst b/docs/source/guides/admin-guides/basic_concepts/xcat_object/node.rst index 15f3d48d3..c72a78d42 100644 --- a/docs/source/guides/admin-guides/basic_concepts/xcat_object/node.rst +++ b/docs/source/guides/admin-guides/basic_concepts/xcat_object/node.rst @@ -13,7 +13,7 @@ Key Attributes The operating system deployed on this node. Valid values: AIX, rhels*, rhelc*, rhas*, centos*, SL*, fedora*, sles* (where * is the version #) * arch: - The hardware architecture of this node. Valid values: x86_64, ppc64, x86, ia64, aarch64. + The hardware architecture of this node. Valid values: x86_64, ppc64, x86, ia64, aarch64, riscv64. * groups: Usually, there are a set of nodes with some attributes in common, xCAT admin can define a node group containing these nodes, so that the management task can be issued against the group instead of individual nodes. A node can be a member of different groups, so the value of this attributes is a comma-delimited list of groups. At least one group is required to create a node. The new created group names should not be prefixed with "__" as this token has been preserved as the internal group name. @@ -44,7 +44,9 @@ Key Attributes | ppc64le PowerKVM Guest | ALL | grub2,grub2-http,grub2-tftp | +--------------------------+----------------------+-----------------------------------+ | aarch64 | >=el8 | grub2 | - +-------------------------------------------------+-----------------------------------+ + +--------------------------+----------------------+-----------------------------------+ + | riscv64 | >=el10 | grub2,grub2-http,grub2-tftp | + +--------------------------+----------------------+-----------------------------------+ * postscripts: Comma separated list of scripts, that should be run on this node after diskful installation or diskless boot, finish some system configuration and maintenance work. For installation of RedHat, CentOS, Fedora, the scripts will be run before the reboot. For installation of SLES, the scripts will be run after the reboot but before the init.d process. diff --git a/docs/source/guides/admin-guides/manage_clusters/index.rst b/docs/source/guides/admin-guides/manage_clusters/index.rst index d44e4a099..e9e8b0d11 100644 --- a/docs/source/guides/admin-guides/manage_clusters/index.rst +++ b/docs/source/guides/admin-guides/manage_clusters/index.rst @@ -11,3 +11,4 @@ The sections are organized based on hardware architecture. ppc64le/index.rst x86_64/index.rst + riscv64/index.rst diff --git a/docs/source/guides/admin-guides/manage_clusters/riscv64/index.rst b/docs/source/guides/admin-guides/manage_clusters/riscv64/index.rst new file mode 100644 index 000000000..faa4ce191 --- /dev/null +++ b/docs/source/guides/admin-guides/manage_clusters/riscv64/index.rst @@ -0,0 +1,157 @@ +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 +:doc:`/guides/admin-guides/manage_clusters/index` applies; this page +only covers what is specific to the architecture. + +What riscv64 nodes need +----------------------- + +* **UEFI firmware that can PXE boot.** riscv64 nodes boot through UEFI and + grub2 only: there is no BIOS/PXELINUX, petitboot or xNBA path. The firmware + sends DHCP option 93 (client system architecture) value 27 (``0x001b``) and + xCAT answers with the boot file ``boot/grub2/grub2.riscv64``, from both the + Kea and the ISC DHCP backends. + Firmware configured for UEFI HTTP boot (client architecture 28, ``0x001c``) + is served the same image over HTTP. +* ``noderes.netboot`` is one of ``grub2``, ``grub2-tftp`` or ``grub2-http``. + ``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. +* 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``), + 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 +:doc:`/advanced/mixed_cluster/support_matrix`) or riscv64 (see below); riscv64 +nodes are managed like any other mixed-architecture cluster. + +Node discovery +-------------- + +``mknb riscv64`` publishes the Genesis kernel and initramfs as +``/tftpboot/xcat/genesis.kernel.riscv64`` and +``/tftpboot/xcat/genesis.fs.riscv64.lzma`` (or ``.gz``) and writes one grub2 configuration per +network, ``/tftpboot/boot/grub2/grub.cfg-``. A net booted +``grub2.riscv64`` looks for ``grub.cfg-01-``, then ``grub.cfg-<8 hex digit +ip>``, then shorter prefixes of that ip; the per-node files written by +``nodeset`` therefore take priority and the network file is only used by nodes +that have no configuration yet, which is exactly the discovery case. +Each configuration holds two entries. The default one fetches the Genesis kernel +and initramfs over HTTP, because a TFTP server hands the image to one client at a +time and the initramfs is tens of megabytes; the second entry loads the same +files over TFTP and is there for a management node that does not serve the TFTP +root over HTTP. ``site.httpport`` is honoured. +``mknb`` runs automatically when the riscv64 Genesis packages are installed or +updated; run ``mknb riscv64`` yourself after changing ``site.master``, +``site.dhcpinterfaces`` or the serial console settings. It also warns when +``grub2.riscv64`` is missing, since nothing would reach these configurations. + +Discovered riscv64 nodes get ``nodetype.arch=riscv64`` and, when no compatible +method is set, ``noderes.netboot=grub2``. Use the discovery procedures documented +for the other architectures. + +Stateful (diskful) installation +-------------------------------- + +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. +``service.rocky10.riscv64.otherpkgs.pkglist`` pulls the service node packages +from the ``rh10/riscv64`` xcat-dep repository. + +The EL10 installer (anaconda) has no RISC-V EFI platform: on riscv64 it asks +for the x86 UEFI boot loader packages (``grub2-efi-x64``, ``shim-x64``), which +do not exist, and registers the UEFI boot entry as ``\EFI\\shimx64.efi``. +xCAT therefore ships riscv64 variants of the EL10 templates and package lists +(``compute.rocky10.riscv64.tmpl``, ``compute.rhels10.riscv64.tmpl`` and the +service profiles): ``%packages --ignoremissing`` tolerates the request, the +package lists add ``grub2-efi-riscv64`` and ``efibootmgr``, and the +``post.rhels10.riscv64`` script run from ``%post`` points the UEFI boot entry at +``\EFI\\grubriscv64.efi`` and places the removable-media fallback loader +``\EFI\BOOT\BOOTRISCV64.EFI``. Custom templates for riscv64 should start from +these files. After ``nodeset boot`` the firmware boots the installed +system because the per-node ``grub2-`` loader link is removed. + +Crash dumps +~~~~~~~~~~~ + +EL10 defines no default crash kernel reservation for riscv64 +(``kdumpctl get-default-crashkernel`` is empty there), so the installer's kdump +add-on writes the literal ``crashkernel=auto``, which the kernel ignores: no +memory is reserved and ``kdump.service`` fails on every installed node. The +riscv64 templates therefore turn that add-on off. To take crash dumps on a +riscv64 node, reserve the memory yourself with a real value, for example:: + + chdef -t osimage rocky10.2-riscv64-install-compute addkcmdline="crashkernel=256M" + +Diskless images use ``linuximage.dump`` and ``linuximage.crashkernelsize`` as on +the other architectures; a riscv64 image with ``dump`` set and no explicit size +reserves 256M. + +Stateless (diskless) images +--------------------------- + +``genimage`` builds riscv64 images with the ``compute.rocky10.riscv64.*`` and +``compute.rhels10.riscv64.*`` profiles (package list, exclude list and +postinstall). On an x86_64 management node this needs ``qemu-user-static`` +registered for riscv64 through ``systemd-binfmt``; see +:doc:`/advanced/mixed_cluster/building_stateless_images`. EL10 has no +``qemu-user-static`` package of its own, so take the static riscv64 emulator +from a distribution that ships one. The default network driver list for +riscv64 images covers virtio, Intel, Realtek, Broadcom and Mellanox adapters; +add others through ``linuximage.netdrivers``. Cross-building on an x86_64 +management node needs the riscv64 user-mode emulator registered with +systemd-binfmt, as described in +:doc:`/advanced/mixed_cluster/building_stateless_images`. + +Management node on riscv64 +-------------------------- + +The EL10 riscv64 BaseOS, AppStream and CRB repositories provide every +dependency that xCAT takes from the distribution on x86_64, including ``kea``. +EPEL has no riscv64 build, so the packages xCAT otherwise takes from EPEL must +come from the riscv64 xcat-dep repository together with the usual xcat-dep +packages: + +* from EPEL on x86_64: ``perl-Digest-SHA1``, ``perl-Net-DNS``, + ``perl-Crypt-CBC`` and ``perl-Crypt-Rijndael``; optional features also use + ``perl-Expect``, ``perl-HTML-Form``, ``perl-Sys-Virt``, ``perl-Mail-Sender``, + ``perl-SOAP-Lite``, ``perl-Crypt-Blowfish``, ``perl-Net-IP`` and + ``conserver``; +* always from xcat-dep: ``perl-Net-Telnet``, ``perl-IO-Stty``, + ``perl-Net-HTTPS-NB``, ``perl-HTTP-Async``, ``perl-Crypt-SSLeay``, + ``goconserver``, ``ipmitool-xcat``, ``conserver-xcat`` and ``grub2-xcat``. + +``perl-DB_File`` is deliberately not in that list: it cannot be built for +riscv64 (EL10 has no libdb). It is only used by the optional Confluent client, +so ``xCAT-server`` recommends it instead of requiring it. On the architectures +that do have it, the weak dependency installs it as before; a management node +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. + +Limitations +----------- + +* UEFI HTTP boot (client architecture 28, ``0x001c``): a client without a + reservation is offered the boot loader as a URL with the ``HTTPClient`` vendor + class, which is what such firmware requires. It has not been exercised against + 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. +* The serial console defaults to ``ttyS``; boards whose + firmware exposes the console on another device need + ``linuximage.addkcmdline`` or the serial settings adjusted. diff --git a/docs/source/guides/admin-guides/references/man1/genimage.1.rst b/docs/source/guides/admin-guides/references/man1/genimage.1.rst index 1d13e6ede..31022bf89 100644 --- a/docs/source/guides/admin-guides/references/man1/genimage.1.rst +++ b/docs/source/guides/admin-guides/references/man1/genimage.1.rst @@ -72,7 +72,7 @@ OPTIONS \ **-a**\ \ *arch*\ - The hardware architecture of this node: ppc64le, x86_64, ppc64, x86, ia64, aarch64 etc. If omitted, the current hardware architecture will be used. + The hardware architecture of this node: ppc64le, x86_64, ppc64, x86, ia64, aarch64, riscv64 etc. If omitted, the current hardware architecture will be used. diff --git a/docs/source/guides/admin-guides/references/man5/noderes.5.rst b/docs/source/guides/admin-guides/references/man5/noderes.5.rst index 67bb5fde7..733a5afab 100644 --- a/docs/source/guides/admin-guides/references/man5/noderes.5.rst +++ b/docs/source/guides/admin-guides/references/man5/noderes.5.rst @@ -62,6 +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 diff --git a/docs/source/guides/admin-guides/references/man5/nodetype.5.rst b/docs/source/guides/admin-guides/references/man5/nodetype.5.rst index 72e6d6f4b..bf10c8f87 100644 --- a/docs/source/guides/admin-guides/references/man5/nodetype.5.rst +++ b/docs/source/guides/admin-guides/references/man5/nodetype.5.rst @@ -50,7 +50,7 @@ nodetype Attributes: \ **arch**\ - The hardware architecture of this node. Valid values: x86_64, ppc64, x86, ia64, aarch64. + The hardware architecture of this node. Valid values: x86_64, ppc64, x86, ia64, aarch64, riscv64. diff --git a/docs/source/guides/admin-guides/references/man5/osimage.5.rst b/docs/source/guides/admin-guides/references/man5/osimage.5.rst index 5ec6a150e..4586a84f2 100644 --- a/docs/source/guides/admin-guides/references/man5/osimage.5.rst +++ b/docs/source/guides/admin-guides/references/man5/osimage.5.rst @@ -115,7 +115,7 @@ osimage Attributes: \ **osarch**\ - The hardware architecture of this node. For netboot/statelite images, QEMU emulation for non-native architectures is used if qemu-user-static is installed and configured via systemd-binfmt. Valid values: x86_64, ppc64, x86, ia64, aarch64. + The hardware architecture of this node. For netboot/statelite images, QEMU emulation for non-native architectures is used if qemu-user-static is installed and configured via systemd-binfmt. Valid values: x86_64, ppc64, x86, ia64, aarch64, riscv64. diff --git a/docs/source/guides/admin-guides/references/man7/group.7.rst b/docs/source/guides/admin-guides/references/man7/group.7.rst index 655dbfc8c..477bda943 100644 --- a/docs/source/guides/admin-guides/references/man7/group.7.rst +++ b/docs/source/guides/admin-guides/references/man7/group.7.rst @@ -47,7 +47,7 @@ group Attributes: \ **arch**\ (nodetype.arch) - The hardware architecture of this node. Valid values: x86_64, ppc64, x86, ia64, aarch64. + The hardware architecture of this node. Valid values: x86_64, ppc64, x86, ia64, aarch64, riscv64. @@ -547,6 +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 diff --git a/docs/source/guides/admin-guides/references/man7/node.7.rst b/docs/source/guides/admin-guides/references/man7/node.7.rst index 14ca6699f..6c290d1d7 100644 --- a/docs/source/guides/admin-guides/references/man7/node.7.rst +++ b/docs/source/guides/admin-guides/references/man7/node.7.rst @@ -59,7 +59,7 @@ node Attributes: \ **arch**\ (nodetype.arch) - The hardware architecture of this node. Valid values: x86_64, ppc64, x86, ia64, aarch64. + The hardware architecture of this node. Valid values: x86_64, ppc64, x86, ia64, aarch64, riscv64. @@ -547,6 +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 diff --git a/docs/source/guides/admin-guides/references/man7/osimage.7.rst b/docs/source/guides/admin-guides/references/man7/osimage.7.rst index cb31b767e..db91bb9d9 100644 --- a/docs/source/guides/admin-guides/references/man7/osimage.7.rst +++ b/docs/source/guides/admin-guides/references/man7/osimage.7.rst @@ -230,7 +230,7 @@ osimage Attributes: \ **osarch**\ (osimage.osarch) - The hardware architecture of this node. For netboot/statelite images, QEMU emulation for non-native architectures is used if qemu-user-static is installed and configured via systemd-binfmt. Valid values: x86_64, ppc64, x86, ia64, aarch64. + The hardware architecture of this node. For netboot/statelite images, QEMU emulation for non-native architectures is used if qemu-user-static is installed and configured via systemd-binfmt. Valid values: x86_64, ppc64, x86, ia64, aarch64, riscv64. diff --git a/docs/source/guides/admin-guides/references/man8/mknb.8.rst b/docs/source/guides/admin-guides/references/man8/mknb.8.rst index de6f39c38..acb753076 100644 --- a/docs/source/guides/admin-guides/references/man8/mknb.8.rst +++ b/docs/source/guides/admin-guides/references/man8/mknb.8.rst @@ -46,6 +46,8 @@ OpenEmbedded images use the exact architecture names \ ``x86``\ , \ ``x86_64``\ Canonical \ ``ppc64``\ images are big-endian. xCAT marks them so \ ``ppc64le``\ nodes do not use them as a legacy little-endian fallback. \ **mknb**\ also refuses to replace a marked \ ``ppc64``\ image with that fallback. +riscv64 nodes boot through UEFI firmware and grub2. For riscv64, \ **mknb**\ publishes the Genesis kernel and initramfs and writes one grub2 configuration per network under ``/tftpboot/boot/grub2``, named ``grub.cfg-`` followed by the network hex prefix, so that ``grub2.riscv64`` loaded by the firmware can start node discovery. The per-node files written by \ **nodeset**\ take priority over these network files. Networks served by a ``:noboot`` interface in ``site.dhcpinterfaces`` get no discovery configuration. + ******* OPTIONS diff --git a/docs/source/guides/install-guides/yum/grub2.rst b/docs/source/guides/install-guides/yum/grub2.rst index ff0b498a3..2f3d6ff1e 100644 --- a/docs/source/guides/install-guides/yum/grub2.rst +++ b/docs/source/guides/install-guides/yum/grub2.rst @@ -1,17 +1,20 @@ -grub2 support for x86_64 and aarch64 -==================================== +grub2 support for x86_64, aarch64 and riscv64 +============================================= -xCAT 2.17 enables grub2 boot support for x86_64 and aarch64 but does not ship the necessary grub2 binaries for both architectures. +xCAT 2.17 enables grub2 boot support for x86_64 and aarch64 but does not ship the necessary grub2 binaries for those architectures. If you want to use grub2 for x86_64 or aarch64 you need to download the binaries from some EL OS repository. +riscv64 nodes can only boot through UEFI firmware and grub2, so they always need ``grub2.riscv64``. ``copycds`` publishes it from the riscv64 installation media, and recent ``grub2-xcat`` packages from xcat-dep install the same image; if your management node has neither (``ls /tftpboot/boot/grub2/grub2.riscv64``), copy the binary by hand as described below. + #. Download files from an BaseOS EL repository mirror - For EL these files are named ``grubx64.efi`` (x86_64) and ``grubaa64.efi`` (aarch64) and usually in ``BaseOS//os/EFI/BOOT``. + For EL these files are named ``grubx64.efi`` (x86_64), ``grubaa64.efi`` (aarch64) and ``grubriscv64.efi`` (riscv64) and usually in ``BaseOS//os/EFI/BOOT``. For very recent hardware you might need to use a newer grub version. Therefore, it's recommended to use a binary from the latest operationg system release available. You can use Red Hat Enterprise Linux, AlmaLinux, Rocky Linux or even Fedora repositories to download the grub files. #. Copy downloaded files to ``/tftpboot/boot/grub2``: - * x86_64: ``/tftpboot/boot/grub2/grub2.aarch64`` - * aarch64: ``/tftpboot/boot/grub2/grub2.x86_64`` + * x86_64: ``/tftpboot/boot/grub2/grub2.x86_64`` + * aarch64: ``/tftpboot/boot/grub2/grub2.aarch64`` + * riscv64: ``/tftpboot/boot/grub2/grub2.riscv64`` diff --git a/docs/source/overview/differentiators.rst b/docs/source/overview/differentiators.rst index 2f4fe5e49..7e9e68206 100644 --- a/docs/source/overview/differentiators.rst +++ b/docs/source/overview/differentiators.rst @@ -16,7 +16,7 @@ Differentiators * Support Multiple Hardware - IBM Power, IBM Power LE, x86_64, aarch64 (alpha support) + IBM Power, IBM Power LE, x86_64, aarch64 (alpha support), riscv64 (EL10, UEFI + grub2) * Support Multiple Virtualization Infrastructures diff --git a/docs/source/overview/support_matrix.rst b/docs/source/overview/support_matrix.rst index d458c6167..6d360f634 100644 --- a/docs/source/overview/support_matrix.rst +++ b/docs/source/overview/support_matrix.rst @@ -1,22 +1,24 @@ Operating System & Hardware Support Matrix ========================================== -+-------+-------+-------+-----+-------+--------+--------+--------+----------+ -| | Power | Power | zVM | Power | x86_64 | x86_64 | x86_64 | aarch64 | -| | | LE | | KVM | | KVM | Esxi | | -+=======+=======+=======+=====+=======+========+========+========+==========+ -|RHEL | yes | yes | yes | yes | yes | yes | yes | yes | -| | | | | | | | | | -+-------+-------+-------+-----+-------+--------+--------+--------+----------+ -|SLES | yes | yes | yes | yes | yes | yes | yes | no | -| | | | | | | | | | -+-------+-------+-------+-----+-------+--------+--------+--------+----------+ -|Ubuntu | no | yes | no | yes | yes | yes | yes | no | -| | | | | | | | | | -+-------+-------+-------+-----+-------+--------+--------+--------+----------+ -|CentOS | no | no | no | no | yes | yes | yes | no | -| | | | | | | | | | -+-------+-------+-------+-----+-------+--------+--------+--------+----------+ -|Windows| no | no | no | no | yes | yes | yes | no | -| | | | | | | | | | -+-------+-------+-------+-----+-------+--------+--------+--------+----------+ ++-------+-------+-------+-----+-------+--------+--------+--------+----------+----------+ +| | Power | Power | zVM | Power | x86_64 | x86_64 | x86_64 | aarch64 | riscv64 | +| | | LE | | KVM | | KVM | Esxi | | | ++=======+=======+=======+=====+=======+========+========+========+==========+==========+ +|RHEL | yes | yes | yes | yes | yes | yes | yes | yes | yes | +| | | | | | | | | | | ++-------+-------+-------+-----+-------+--------+--------+--------+----------+----------+ +|SLES | yes | yes | yes | yes | yes | yes | yes | no | no | +| | | | | | | | | | | ++-------+-------+-------+-----+-------+--------+--------+--------+----------+----------+ +|Ubuntu | no | yes | no | yes | yes | yes | yes | no | no | +| | | | | | | | | | | ++-------+-------+-------+-----+-------+--------+--------+--------+----------+----------+ +|CentOS | no | no | no | no | yes | yes | yes | no | no | +| | | | | | | | | | | ++-------+-------+-------+-----+-------+--------+--------+--------+----------+----------+ +|Windows| no | no | no | no | yes | yes | yes | no | no | +| | | | | | | | | | | ++-------+-------+-------+-----+-------+--------+--------+--------+----------+----------+ + +riscv64 support covers EL10 compute nodes (Rocky Linux 10 and the RHEL 10 RISC-V developer preview) that boot through UEFI firmware and grub2. See :doc:`/guides/admin-guides/manage_clusters/riscv64/index` for details. diff --git a/perl-xCAT/xCAT/DHCP/Backend/Kea.pm b/perl-xCAT/xCAT/DHCP/Backend/Kea.pm index 23524afaa..d4a54ba9d 100644 --- a/perl-xCAT/xCAT/DHCP/Backend/Kea.pm +++ b/perl-xCAT/xCAT/DHCP/Backend/Kea.pm @@ -89,7 +89,8 @@ sub render_dhcp4_config { $dhcp4{'hooks-libraries'} = $intent->{'hooks-libraries'} if $intent->{'hooks-libraries'}; $dhcp4{'option-def'} = $intent->{'option-def'} if $intent->{'option-def'}; $dhcp4{'client-classes'} = [ map { $self->_render_client_class($_) } @{ $intent->{'client-classes'} } ] if $intent->{'client-classes'}; - $dhcp4{'option-data'} = $intent->{'option-data'} if $intent->{'option-data'}; + $dhcp4{'option-data'} = _render_option_data( $intent->{'option-data'} ) + if $intent->{'option-data'}; $dhcp4{'dhcp-ddns'} = $intent->{'dhcp-ddns'} if $intent->{'dhcp-ddns'}; foreach my $field (qw/ddns-send-updates ddns-override-no-update ddns-override-client-update ddns-qualifying-suffix ddns-update-on-renew/) { $dhcp4{$field} = $intent->{$field} if exists $intent->{$field}; @@ -119,7 +120,8 @@ sub render_dhcp6_config { $dhcp6{'control-socket'} = $intent->{'control-socket'} if $intent->{'control-socket'}; $dhcp6{'hooks-libraries'} = $intent->{'hooks-libraries'} if $intent->{'hooks-libraries'}; - $dhcp6{'option-data'} = $intent->{'option-data'} if $intent->{'option-data'}; + $dhcp6{'option-data'} = _render_option_data( $intent->{'option-data'} ) + if $intent->{'option-data'}; $dhcp6{'dhcp-ddns'} = $intent->{'dhcp-ddns'} if $intent->{'dhcp-ddns'}; foreach my $field (qw/ddns-send-updates ddns-override-no-update ddns-override-client-update ddns-qualifying-suffix ddns-update-on-renew/) { $dhcp6{$field} = $intent->{$field} if exists $intent->{$field}; @@ -696,11 +698,11 @@ sub live_upsert_reservations { my $delete = $self->_live_delete_reservation($reservation, service => $service, ignore_not_found => 1); return $delete if $delete->{error}; - my %stored = %$reservation; + my ($stored) = @{ _render_entry_option_data( [$reservation] ) }; my $result = $self->control_agent_command( 'reservation-add', { - reservation => \%stored, + reservation => $stored, 'operation-target' => 'memory', }, service => $service, @@ -767,7 +769,9 @@ sub _render_subnet4 { $rendered{interface} = $subnet->{interface} if defined $subnet->{interface}; $rendered{'next-server'} = _first_defined( $subnet->{'next-server'}, $subnet->{next_server} ); $rendered{'boot-file-name'} = _first_defined( $subnet->{'boot-file-name'}, $subnet->{boot_file_name} ); - $rendered{'option-data'} = _first_defined( $subnet->{'option-data'}, $subnet->{option_data} ); + $rendered{'option-data'} = _render_option_data( + _first_defined( $subnet->{'option-data'}, $subnet->{option_data} ) + ); my $additional_classes = _first_defined( $subnet->{additional_client_classes}, $subnet->{'evaluate-additional-classes'}, @@ -776,13 +780,14 @@ sub _render_subnet4 { $subnet->{require_client_classes}, ); $rendered{ $self->_additional_class_list_field() } = $additional_classes if defined $additional_classes; - $rendered{reservations} = $subnet->{reservations} if $subnet->{reservations}; + $rendered{reservations} = _render_entry_option_data( $subnet->{reservations} ) + if $subnet->{reservations}; delete $rendered{'next-server'} unless defined $rendered{'next-server'}; delete $rendered{'boot-file-name'} unless defined $rendered{'boot-file-name'}; delete $rendered{'option-data'} unless defined $rendered{'option-data'}; if ( $subnet->{pools} ) { - $rendered{pools} = $subnet->{pools}; + $rendered{pools} = _render_entry_option_data( $subnet->{pools} ); } elsif ( $subnet->{dynamicrange} ) { $rendered{pools} = [ xCAT::DHCP::Range->kea_pools( $subnet->{dynamicrange} ) ]; } else { @@ -809,10 +814,55 @@ sub _render_client_class { delete $rendered{'only-if-required'}; $rendered{ $self->_additional_class_flag_field() } = _json_bool($additional_only) if defined $additional_only; + $rendered{'option-data'} = _render_option_data( $rendered{'option-data'} ) + if $rendered{'option-data'}; return \%rendered; } +# Kea reads the option flags as booleans, so the callers can set them the plain +# Perl way and still produce a configuration the server parses. +sub _render_option_data { + my ($option_data) = @_; + + return $option_data unless ref($option_data) eq 'ARRAY'; + + my @rendered; + foreach my $option (@$option_data) { + if ( ref($option) ne 'HASH' ) { + push @rendered, $option; + next; + } + my %copy = %$option; + foreach my $flag (qw/always-send csv-format never-send/) { + $copy{$flag} = _json_bool( $copy{$flag} ) if defined $copy{$flag}; + } + push @rendered, \%copy; + } + + return \@rendered; +} + +sub _render_entry_option_data { + my ($entries) = @_; + + return $entries unless ref($entries) eq 'ARRAY'; + + my @rendered; + foreach my $entry (@$entries) { + if ( ref($entry) ne 'HASH' ) { + push @rendered, $entry; + next; + } + my %copy = %$entry; + $copy{'option-data'} = _render_option_data( $copy{'option-data'} ) + if $copy{'option-data'}; + push @rendered, \%copy; + } + + return \@rendered; +} + sub _render_subnet6 { my ( $self, $subnet ) = @_; @@ -822,12 +872,15 @@ sub _render_subnet6 { ); $rendered{interface} = $subnet->{interface} if defined $subnet->{interface}; - $rendered{'option-data'} = _first_defined( $subnet->{'option-data'}, $subnet->{option_data} ); - $rendered{reservations} = $subnet->{reservations} if $subnet->{reservations}; + $rendered{'option-data'} = _render_option_data( + _first_defined( $subnet->{'option-data'}, $subnet->{option_data} ) + ); + $rendered{reservations} = _render_entry_option_data( $subnet->{reservations} ) + if $subnet->{reservations}; delete $rendered{'option-data'} unless defined $rendered{'option-data'}; if ( $subnet->{pools} ) { - $rendered{pools} = $subnet->{pools}; + $rendered{pools} = _render_entry_option_data( $subnet->{pools} ); } elsif ( $subnet->{dynamicrange} ) { $rendered{pools} = [ xCAT::DHCP::Range->kea_pools( $subnet->{dynamicrange} ) ]; } else { diff --git a/perl-xCAT/xCAT/DHCP/BootPolicy.pm b/perl-xCAT/xCAT/DHCP/BootPolicy.pm index c24992d8c..3c6c976ef 100644 --- a/perl-xCAT/xCAT/DHCP/BootPolicy.pm +++ b/perl-xCAT/xCAT/DHCP/BootPolicy.pm @@ -36,6 +36,11 @@ sub kea_client_classes { test => 'option[93].hex == 0x000b', 'boot-file-name' => 'boot/grub2/grub2.aarch64', }, + { + name => 'xcat-riscv64', + test => 'option[93].hex == 0x001b', + 'boot-file-name' => 'boot/grub2/grub2.riscv64', + }, { name => 'xcat-ppc64', test => 'option[93].hex == 0x000c', @@ -51,6 +56,96 @@ sub kea_client_classes { return \@classes; } +# Architectures whose UEFI firmware can also boot over HTTP, by DHCP client +# architecture id (RFC 4578 and the IANA registry). An HTTP boot client wants the +# boot file as a URL and only accepts the offer when the reply is tagged +# HTTPClient; the image it downloads is the same grub2 the TFTP path hands out. +my %HTTP_BOOT_ARCHES = ( + riscv64 => { arch_id => '0x001c', loader => 'boot/grub2/grub2.riscv64' }, +); + +# The HTTP boot classes of one network. They carry the address of the management +# node on that network, so they belong to the subnet rather than to the global +# list, like the other network classes here. +sub kea_httpboot_network_classes { + my ( $class, %opts ) = @_; + + return [] unless $opts{net} && defined( $opts{prefix} ) && $opts{next_server}; + + my $httpport = $opts{httpport} || '80'; + my $portsuffix = ( $httpport eq '80' ) ? '' : ":$httpport"; + my $tftpdir = $opts{tftpdir} || '/tftpboot'; + $tftpdir =~ s{/+$}{}; + my $http_tftp_root = '/tftpboot'; + my $present = $opts{loader_present}; + my @classes; + + foreach my $arch ( sort keys %HTTP_BOOT_ARCHES ) { + my $spec = $HTTP_BOOT_ARCHES{$arch}; + next if $present && !$present->("$tftpdir/$spec->{loader}"); + my $name = "xcat-$arch-http-$opts{net}_$opts{prefix}"; + $name =~ s/[^A-Za-z0-9_.-]/_/g; + push @classes, { + name => $name, + test => "option[93].hex == $spec->{arch_id}", + additional_only => 1, + 'boot-file-name' => "http://$opts{next_server}$portsuffix$http_tftp_root/$spec->{loader}", + 'option-data' => [ + { + name => 'vendor-class-identifier', + data => 'HTTPClient', + 'always-send' => 1, + }, + ], + }; + } + + return \@classes; +} + +sub isc_client_architecture_lines { + my ( $class, %opts ) = @_; + + my $tftp = $opts{next_server} // ''; + my $portsuffix = $opts{portsuffix} // ''; + my $net = $opts{net} // ''; + my $maskbits = $opts{prefix} // ''; + + return [ + " if option user-class-identifier = \"xNBA\" and option client-architecture = 00:00 { #x86, xCAT Network Boot Agent\n", + " always-broadcast on;\n", + " filename = \"http://$tftp$portsuffix/tftpboot/xcat/xnba/nets/${net}_${maskbits}\";\n", + " } else if option user-class-identifier = \"xNBA\" and option client-architecture = 00:09 { #x86, xCAT Network Boot Agent\n", + " filename = \"http://$tftp$portsuffix/tftpboot/xcat/xnba/nets/${net}_${maskbits}.uefi\";\n", + " } else if option user-class-identifier = \"xNBA\" and option client-architecture = 00:07 { #x86-64 UEFI, xCAT Network Boot Agent\n", + " filename = \"http://$tftp$portsuffix/tftpboot/xcat/xnba/nets/${net}_${maskbits}.uefi\";\n", + " } else if option client-architecture = 00:00 { #x86\n", + " filename \"xcat/xnba.kpxe\";\n", + " } else if option vendor-class-identifier = \"Etherboot-5.4\" { #x86\n", + " filename \"xcat/xnba.kpxe\";\n", + " } else if option client-architecture = 00:07 { #x86_64 uefi\n ", + " filename \"xcat/xnba.efi\";\n", + " } else if option client-architecture = 00:09 { #x86_64 uefi alternative id\n ", + " filename \"xcat/xnba.efi\";\n", + " } else if option client-architecture = 00:02 { #ia64\n ", + " filename \"elilo.efi\";\n", + " } else if option client-architecture = 00:0b { #aaarch64\n ", + " filename \"boot/grub2/grub2.aarch64\";\n", + " } else if option client-architecture = 00:1b { #riscv64 uefi\n ", + " filename \"boot/grub2/grub2.riscv64\";\n", + " } else if option client-architecture = 00:1c { #riscv64 uefi http boot\n ", + " option vendor-class-identifier \"HTTPClient\";\n", + " filename \"http://$tftp$portsuffix/tftpboot/boot/grub2/grub2.riscv64\";\n", + " } else if option client-architecture = 00:0e { #OPAL-v3\n ", + " option conf-file = \"http://$tftp$portsuffix/tftpboot/pxelinux.cfg/p/${net}_${maskbits}\";\n", + " } else if substring (option vendor-class-identifier,0,11) = \"onie_vendor\" { #for onie on cumulus switch\n", + " option www-server = \"http://$tftp$portsuffix/install/onie/onie-installer\";\n", + " } else if substring(filename,0,1) = null { #otherwise, provide yaboot if the client isn't specific\n ", + " filename \"/yaboot\";\n", + " }\n", + ]; +} + sub kea_xnba_node_classes { my ( $class, %opts ) = @_; diff --git a/perl-xCAT/xCAT/ProfiledNodeUtils.pm b/perl-xCAT/xCAT/ProfiledNodeUtils.pm index b901d2413..4c634072d 100644 --- a/perl-xCAT/xCAT/ProfiledNodeUtils.pm +++ b/perl-xCAT/xCAT/ProfiledNodeUtils.pm @@ -1429,6 +1429,27 @@ sub get_all_vmhosts =cut #------------------------------------------------------------------------------- +our %NETBOOT_RULES = ( + 'x86_64' => 'xnba', + 'riscv64' => 'grub2', + 'ppc64' => { + 'rhels' => { + '7' => 'grub2', + '*' => 'yaboot', + }, + 'pkvm' => 'petitboot', + '*' => 'yaboot', + }, + 'ppc64le' => { + '*' => { + '*' => { + '*' => 'grub2', + 'ipmi' => 'petitboot', + }, + }, + }, +); + sub get_netboot_attr { my $class = shift; my $imageprofile = shift; @@ -1502,27 +1523,10 @@ sub get_netboot_attr { # 3 | | * | * | * | yaboot | # 4 | ppc64le/el | * | * | * | grub2 # 4 | ppc64le/el | * | * | ipmi | petitboot + # 5 | riscv64 | * | * | * | grub2 | # arch osname version hardware netboot - my %netboot_dict = ('x86_64' => 'xnba', - 'ppc64' => { - 'rhels' => { - '7' => 'grub2', - '*' => 'yaboot', - }, - 'pkvm' => 'petitboot', - '*' => 'yaboot', - }, - 'ppc64le' => { - '*' => { - '*' => { - '*' => 'grub2', - 'ipmi' => 'petitboot', - }, - }, - }, - ); my $condition_array_ref = [ $os_arch, $os_name, $os_major_version, $mgt ]; - $netboot = cal_netboot(\%netboot_dict, $condition_array_ref); + $netboot = cal_netboot(\%NETBOOT_RULES, $condition_array_ref); if ($netboot eq '0') { return 0, "Can not get the netboot attribute"; diff --git a/perl-xCAT/xCAT/Schema.pm b/perl-xCAT/xCAT/Schema.pm index 796201ecd..4fcc49c6d 100644 --- a/perl-xCAT/xCAT/Schema.pm +++ b/perl-xCAT/xCAT/Schema.pm @@ -676,6 +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 ', tftpserver => 'The TFTP server for this node (as known by this node). If not set, it defaults to networks.tftpserver.', @@ -750,7 +751,7 @@ passed as argument rather than by table value', descriptions => { node => 'The node name or group name.', os => 'The operating system deployed on this node. Valid values: AIX, rhels*,rhelc*, rhas*,centos*, alma*, rocky*,SL*, fedora*, sles* (where * is the version #). As a special case, if this is set to "boottarget", then it will use the initrd/kernel/parameters specified in the row in the boottarget table in which boottarget.bprofile equals nodetype.profile.', - arch => 'The hardware architecture of this node. Valid values: x86_64, ppc64, x86, ia64, aarch64.', + arch => 'The hardware architecture of this node. Valid values: x86_64, ppc64, x86, ia64, aarch64, riscv64.', profile => 'The string to use to locate a kickstart or autoyast template to use for OS deployment of this node. If the provmethod attribute is set to an osimage name, that takes precedence, and profile need not be defined. Otherwise, the os, profile, and arch are used to search for the files in /install/custom first, and then in /opt/xcat/share/xcat.', provmethod => 'The provisioning method for node deployment. The valid values are install, netboot, statelite or an os image name from the osimage table. If an image name is specified, the osimage definition stored in the osimage table and the linuximage table (for Linux) or nimimage table (for AIX) are used to locate the files for templates, pkglists, syncfiles, etc. On Linux, if install, netboot or statelite is specified, the os, profile, and arch are used to search for the files in /install/custom first, and then in /opt/xcat/share/xcat.', supportedarchs => 'Comma delimited list of architectures this node can execute.', @@ -794,7 +795,7 @@ passed as argument rather than by table value', profile => 'The node usage category. For example compute, service.', osname => 'Operating system name- AIX or Linux.', osvers => 'The Linux operating system deployed on this node. Valid values: rhels*,rhelc*, rhas*,centos*,alma*, rocky*,SL*, fedora*, sles* (where * is the version #).', - osarch => 'The hardware architecture of this node. For netboot/statelite images, QEMU emulation for non-native architectures is used if qemu-user-static is installed and configured via systemd-binfmt. Valid values: x86_64, ppc64, x86, ia64, aarch64.', + osarch => 'The hardware architecture of this node. For netboot/statelite images, QEMU emulation for non-native architectures is used if qemu-user-static is installed and configured via systemd-binfmt. Valid values: x86_64, ppc64, x86, ia64, aarch64, riscv64.', synclists => 'The fully qualified name of a file containing a list of files to synchronize on the nodes. Can be a comma separated list of multiple synclist files. The synclist generated by PCM named /install/osimages//synclist.cfm is reserved for use only by PCM and should not be edited by the admin.', postscripts => 'Comma separated list of scripts that should be run on this image after diskful installation or diskless boot. For installation of RedHat, CentOS, Fedora, the scripts will be run before the reboot. For installation of SLES, the scripts will be run after the reboot but before the init.d process. For diskless deployment, the scripts will be run at the init.d time, and xCAT will automatically add the list of scripts from the postbootscripts attribute to run after postscripts list. For installation of AIX, the scripts will run after the reboot and acts the same as the postbootscripts attribute. For AIX, use the postbootscripts attribute. See the site table runbootscripts attribute.', postbootscripts => 'Comma separated list of scripts that should be run on this after diskful installation or diskless boot. On AIX these scripts are run during the processing of /etc/inittab. On Linux they are run at the init.d time. xCAT automatically adds the scripts in the xcatdefaults.postbootscripts attribute to run first in the list. See the site table runbootscripts attribute.', diff --git a/perl-xCAT/xCAT/Utils.pm b/perl-xCAT/xCAT/Utils.pm index 65c253a7d..81256ab93 100644 --- a/perl-xCAT/xCAT/Utils.pm +++ b/perl-xCAT/xCAT/Utils.pm @@ -4916,6 +4916,8 @@ sub lookupNetboot { $ret = "petitboot,grub2,grub2-tftp,grub2-http"; } elsif ($osarch =~ /^aarch64$/i) { $ret = "grub2"; + } elsif ($osarch =~ /^riscv64$/i) { + $ret = "grub2,grub2-tftp,grub2-http"; } } return $ret; diff --git a/xCAT-client/pods/man1/genimage.1.pod b/xCAT-client/pods/man1/genimage.1.pod index 60d9c78be..48633e597 100644 --- a/xCAT-client/pods/man1/genimage.1.pod +++ b/xCAT-client/pods/man1/genimage.1.pod @@ -50,7 +50,7 @@ I specifies the name of an os image definition to be used. The specif =item B<-a> I -The hardware architecture of this node: ppc64le, x86_64, ppc64, x86, ia64, aarch64 etc. If omitted, the current hardware architecture will be used. +The hardware architecture of this node: ppc64le, x86_64, ppc64, x86, ia64, aarch64, riscv64 etc. If omitted, the current hardware architecture will be used. =item B<-o> I diff --git a/xCAT-client/pods/man8/mknb.8.pod b/xCAT-client/pods/man8/mknb.8.pod index 1e377b0cc..6ffa74517 100644 --- a/xCAT-client/pods/man8/mknb.8.pod +++ b/xCAT-client/pods/man8/mknb.8.pod @@ -28,6 +28,8 @@ OpenEmbedded images use the exact architecture names C, C, C Canonical C images are big-endian. xCAT marks them so C nodes do not use them as a legacy little-endian fallback. B also refuses to replace a marked C image with that fallback. +riscv64 nodes boot through UEFI firmware and grub2. For riscv64, B publishes the Genesis kernel and initramfs and writes one grub2 configuration per network under C, named C followed by the network hex prefix, so that C loaded by the firmware can start node discovery. The per-node files written by B take priority over these network files. Networks served by a C<:noboot> interface in C get no discovery configuration. + =head1 OPTIONS =over 12 diff --git a/xCAT-server/lib/xcat/plugins/anaconda.pm b/xCAT-server/lib/xcat/plugins/anaconda.pm index 9107f26ed..07c5945db 100644 --- a/xCAT-server/lib/xcat/plugins/anaconda.pm +++ b/xCAT-server/lib/xcat/plugins/anaconda.pm @@ -157,6 +157,44 @@ sub _find_genesis_boot_files return ($kernel, $initrd); } +sub _install_media_pxeboot_paths +{ + my ($pkgdir, $arch) = @_; + + return unless defined($pkgdir) && defined($arch); + return unless $arch =~ /x86|aarch64|riscv64/; + + my $kernel = "$pkgdir/images/pxeboot/vmlinuz"; + my $initrd = "$pkgdir/images/pxeboot/initrd.img"; + return unless -r $kernel; + + return ($kernel, -r $initrd ? $initrd : undef); +} + +sub _driver_disk_kernel_version +{ + my $path = shift; + + return unless defined($path); + return $1 if $path =~ m{/vmlinuz-(.*(?:x86_64|ppc64|el\d+|ppc64le|aarch64|riscv64))$}; + return; +} + +sub _default_crashkernel_args +{ + my ($arch, $dump, $fadump_flag, $fadump, $kdump) = @_; + + return '' unless defined($arch); + if ($arch eq 'ppc64') { + return $fadump_flag + ? " fadump=on fadump_reserve_mem=512M fadump_target=$fadump fadump_default=noreboot dump=$kdump " + : " crashkernel=256M\@64M dump=$dump "; + } + return " crashkernel=128M dump=$dump " if $arch =~ /86/; + return " crashkernel=256M dump=$dump " if $arch eq 'riscv64'; + return ''; +} + sub preprocess_request { my $req = shift; @@ -907,17 +945,9 @@ sub mknetboot } } else { - if ($arch eq "ppc64") { - if ($fadumpFlag) { - $kcmdline .= " fadump=on fadump_reserve_mem=512M fadump_target=$fadump fadump_default=noreboot dump=$kdump "; - } - else { - $kcmdline .= " crashkernel=256M\@64M dump=$dump "; - } - } - if ($arch =~ /86/) { - $kcmdline .= " crashkernel=128M dump=$dump "; - } + $kcmdline .= _default_crashkernel_args( + $arch, $dump, $fadumpFlag, $fadump, $kdump + ); } } @@ -1340,15 +1370,14 @@ sub mkinstall $pkvm = 1; } + my ($pxe_kernpath, $pxe_initrdpath) = + _install_media_pxeboot_paths($pkgdir, $arch); + $kernpath = $pxe_kernpath if defined($pxe_kernpath); + $initrdpath = $pxe_initrdpath if defined($pxe_initrdpath); + if ( ( - ( $arch =~ /x86/ or $arch =~ /aarch64/ ) and - ( - -r "$pkgdir/images/pxeboot/vmlinuz" - and $kernpath = "$pkgdir/images/pxeboot/vmlinuz" - and -r "$pkgdir/images/pxeboot/initrd.img" - and $initrdpath = "$pkgdir/images/pxeboot/initrd.img" - ) or ( #Handle the case seen in VMWare 4.0 ESX media + ( defined($pxe_kernpath) and defined($pxe_initrdpath) ) or ( #Handle the case seen in VMWare 4.0 ESX media #In VMWare 4.0 they dropped the pxe-optimized initrd #leaving us no recourse but the rather large optical disk #initrd, but perhaps we can mitigate with gPXE @@ -2086,6 +2115,36 @@ erver, if so, stop it first and try again" ], } } +# EL media that carry the grub2 UEFI image of an architecture xCAT cannot build a +# boot loader for. copycd publishes it so those nodes can net boot without any +# further step; grub2-xcat installs the same image where it is available. +my %MEDIA_GRUB2_LOADERS = (riscv64 => 'grubriscv64.efi'); + +# Publish the grub2 UEFI image of the media under $tftpdir/boot/grub2, unless the +# management node already has one. Returns the path written, or undef. +sub _install_media_grub2_loader { + my ($path, $arch, $callback) = @_; + + my $image = $MEDIA_GRUB2_LOADERS{$arch}; + return unless $image; + my $source = "$path/EFI/BOOT/$image"; + return unless -r $source; + + my $tftpdir = xCAT::TableUtils->getTftpDir(); + return unless $tftpdir; + my $target = "$tftpdir/boot/grub2/grub2.$arch"; + return if -e $target; + + mkpath("$tftpdir/boot/grub2"); + unless (copy($source, $target)) { + $callback->({ data => "Could not install $target from the media: $!" }) if $callback; + return; + } + chmod 0644, $target; + $callback->({ data => "Installed $target from the media" }) if $callback; + return $target; +} + sub copycd { my $request = shift; @@ -2483,6 +2542,7 @@ sub copycd else { $callback->({ data => "Media copy operation successful" }); + _install_media_grub2_loader($path, $arch, $callback); my @ret = xCAT::SvrUtils->update_osdistro_table($distname, $arch, $path, $osdistroname); if ($ret[0] != 0) { $callback->({ data => "Error when updating the osdistro tables: " . $ret[1] }); @@ -2798,8 +2858,9 @@ sub insert_dd { # and copy it to the /tftpboot my @new_kernels = <$dd_dir/rpm/boot/vmlinuz*>; foreach my $new_kernel (@new_kernels) { - if (-r $new_kernel && $new_kernel =~ /\/vmlinuz-(.*(x86_64|ppc64|el\d+|ppc64le|aarch64))$/) { - $new_kernel_ver = $1; + my $candidate_version = _driver_disk_kernel_version($new_kernel); + if (-r $new_kernel && defined($candidate_version)) { + $new_kernel_ver = $candidate_version; $cmd = "/bin/mv -f $new_kernel $kernelpath"; xCAT::Utils->runcmd($cmd, -1); if ($::RUNCMD_RC != 0) { diff --git a/xCAT-server/lib/xcat/plugins/dhcp.pm b/xCAT-server/lib/xcat/plugins/dhcp.pm index c6afb2adb..150f8613d 100644 --- a/xCAT-server/lib/xcat/plugins/dhcp.pm +++ b/xCAT-server/lib/xcat/plugins/dhcp.pm @@ -3289,6 +3289,16 @@ sub kea_subnet4_intent $subnet{interface} = $interface unless $remote; my @client_classes = @$xnba_classes; push @client_classes, $opal_class if $opal_class; + push @client_classes, @{ + xCAT::DHCP::BootPolicy->kea_httpboot_network_classes( + net => $net, + prefix => $prefix, + next_server => $tftp, + httpport => $httpport, + tftpdir => $tftpdir, + loader_present => sub { -e $_[0] }, + ) + }; if (@client_classes) { $subnet{additional_client_classes} = [ map { $_->{name} } @client_classes ]; $subnet{client_classes} = \@client_classes; @@ -4444,39 +4454,13 @@ sub addnet } # $lstatements = 'if exists gpxe.bus-id { filename = \"\"; } else if exists client-architecture { filename = \"xcat/xnba.kpxe\"; } '.$lstatements; - push @netent, " if option user-class-identifier = \"xNBA\" and option client-architecture = 00:00 { #x86, xCAT Network Boot Agent\n"; - push @netent, " always-broadcast on;\n"; - push @netent, " filename = \"http://$tftp$portsuffix/tftpboot/xcat/xnba/nets/" . $net . "_" . $maskbits . "\";\n"; - push @netent, " } else if option user-class-identifier = \"xNBA\" and option client-architecture = 00:09 { #x86, xCAT Network Boot Agent\n"; - push @netent, " filename = \"http://$tftp$portsuffix/tftpboot/xcat/xnba/nets/" . $net . "_" . $maskbits . ".uefi\";\n"; - push @netent, " } else if option user-class-identifier = \"xNBA\" and option client-architecture = 00:07 { #x86-64 UEFI, xCAT Network Boot Agent\n"; - push @netent, " filename = \"http://$tftp$portsuffix/tftpboot/xcat/xnba/nets/" . $net . "_" . $maskbits . ".uefi\";\n"; - push @netent, " } else if option client-architecture = 00:00 { #x86\n"; - push @netent, " filename \"xcat/xnba.kpxe\";\n"; - push @netent, " } else if option vendor-class-identifier = \"Etherboot-5.4\" { #x86\n"; - push @netent, " filename \"xcat/xnba.kpxe\";\n"; - push @netent, - " } else if option client-architecture = 00:07 { #x86_64 uefi\n "; - push @netent, " filename \"xcat/xnba.efi\";\n"; - push @netent, - " } else if option client-architecture = 00:09 { #x86_64 uefi alternative id\n "; - push @netent, " filename \"xcat/xnba.efi\";\n"; - push @netent, - " } else if option client-architecture = 00:02 { #ia64\n "; - push @netent, " filename \"elilo.efi\";\n"; - push @netent, - " } else if option client-architecture = 00:0b { #aaarch64\n "; - push @netent, " filename \"boot/grub2/grub2.aarch64\";\n"; - push @netent, - " } else if option client-architecture = 00:0e { #OPAL-v3\n "; - push @netent, " option conf-file = \"http://$tftp$portsuffix/tftpboot/pxelinux.cfg/p/" . $net . "_" . $maskbits . "\";\n"; - push @netent, - " } else if substring (option vendor-class-identifier,0,11) = \"onie_vendor\" { #for onie on cumulus switch\n"; - push @netent, " option www-server = \"http://$tftp$portsuffix/install/onie/onie-installer\";\n"; - push @netent, - " } else if substring(filename,0,1) = null { #otherwise, provide yaboot if the client isn't specific\n "; - push @netent, " filename \"/yaboot\";\n"; - push @netent, " }\n"; + push @netent, @{ xCAT::DHCP::BootPolicy->isc_client_architecture_lines( + next_server => $tftp, + portsuffix => $portsuffix, + tftpdir => $tftpdir, + net => $net, + prefix => $maskbits, + ) }; if ($range) { foreach my $singlerange (split /;/, $range) { diff --git a/xCAT-server/lib/xcat/plugins/geninitrd.pm b/xCAT-server/lib/xcat/plugins/geninitrd.pm index 157280ee7..6f9388cef 100644 --- a/xCAT-server/lib/xcat/plugins/geninitrd.pm +++ b/xCAT-server/lib/xcat/plugins/geninitrd.pm @@ -15,6 +15,14 @@ use xCAT::TableUtils; use xCAT::Table; use xCAT::Scope; +sub _uses_x86_install_media_layout +{ + my ($arch, $osvers) = @_; + + return unless defined($arch) && defined($osvers); + return $arch =~ /x86/ || ($arch =~ /riscv64/ && $osvers !~ /sles|suse/); +} + sub handled_commands { return { @@ -189,7 +197,9 @@ sub geninitrd { unless (-d $tftppath) { mkpath $tftppath; } - if ($arch =~ /x86/) { + # riscv64 installation media exist for the EL family only; leave any other + # distribution to the "unknow arch" error below instead of reading it as x86 + if (_uses_x86_install_media_layout($arch, $osvers)) { if ($osvers =~ /(^ol[0-9].*)|(centos.*)|(alma.*)|(rocky.*)|(rh.*)|(fedora.*)|(SL.*)/) { $kernelpath = "$tftppath/vmlinuz"; copy("$pkgdir/images/pxeboot/vmlinuz", $kernelpath); diff --git a/xCAT-server/lib/xcat/plugins/mknb.pm b/xCAT-server/lib/xcat/plugins/mknb.pm index a3a2a5b91..891310e2d 100644 --- a/xCAT-server/lib/xcat/plugins/mknb.pm +++ b/xCAT-server/lib/xcat/plugins/mknb.pm @@ -18,6 +18,14 @@ sub _canonical_genesis_arch { return $arch eq 'ppc64el' ? 'ppc64le' : $arch; } +# Architectures whose discovery boot goes through UEFI firmware and grub2. +# mknb writes one grub2 configuration per network for them. The value is the +# $grub_cpu string reported by that architecture's GRUB build, so one file can +# carry a menu entry per architecture that shares a network. +my %GRUB2_DISCOVERY_ARCHES = ( + riscv64 => 'riscv64', +); + sub handled_commands { return { mknb => 'mknb', @@ -703,6 +711,9 @@ sub process_request { } } elsif ($arch =~ /ppc/) { mkpath("$tftpdir/pxelinux.cfg/p/"); + } elsif (exists $GRUB2_DISCOVERY_ARCHES{$arch}) { + mkpath("$tftpdir/boot/grub2"); + chmod(0755, "$tftpdir/boot/grub2"); } my $dopxe = 0; foreach (keys %{$normnets}) { @@ -816,11 +827,114 @@ sub process_request { print $cfgfile " initrd http://" . $xcatd_address . "$portsuffix/$initrd_file\n"; print $cfgfile ' append "xcatd=' . $xcatd_address . ":$xcatdport $consolecmdline\"\n"; close($cfgfile); + } elsif (exists $GRUB2_DISCOVERY_ARCHES{$arch}) { + # Also drop it when the xcatd address chosen for the network sits on a + # :noboot interface; the PXELINUX files only check the legacy address. + if (defined($nobootnicips{ $hexnets->{$_} }) or defined($nobootnicips{$xcatd_address})) { + unlink("$tftpdir/boot/grub2/grub.cfg-" . uc($_)); + next; + } + _write_grub2_discovery_config( + tftpdir => $tftpdir, + hexnet => $_, + xcatd_address => $xcatd_address, + xcatdport => $xcatdport, + httpport => $httpport, + consolecmdline => $consolecmdline, + ); } } + if (exists $GRUB2_DISCOVERY_ARCHES{$arch} && !-e "$tftpdir/boot/grub2/grub2.$arch") { + # These configurations are only reachable through grub2., 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)"] }); + } if ($configfileonly) { $callback->({ data => ["Write netboot config file done"] }); } } +# Return the grub2-class architectures whose Genesis kernel and initrd are +# published under $tftpdir/xcat, as [arch, grub_cpu, kernel, initrd] with the +# file names relative to the TFTP root. + +sub _grub2_discovery_arches { + my ($tftpdir) = @_; + my @present; + foreach my $arch (sort keys %GRUB2_DISCOVERY_ARCHES) { + next unless -e "$tftpdir/xcat/genesis.kernel.$arch"; + my ($initrd) = grep { -e "$tftpdir/$_" } + map { "xcat/genesis.fs.$arch.$_" } qw(lzma gz); + next unless $initrd; + push @present, + [ $arch, $GRUB2_DISCOVERY_ARCHES{$arch}, "xcat/genesis.kernel.$arch", $initrd ]; + } + return @present; +} + +# Write the grub2 discovery configuration for one network. +# +# grub2., net booted from $tftpdir/boot/grub2, looks for grub.cfg-01-, +# then grub.cfg-<8 hex digit ip>, then ever shorter prefixes of that ip, and +# finally grub.cfg. nodeset writes the per-node files (full ip and mac), so a +# per-network prefix is only reached by clients without a node configuration: +# discovery. The file is rebuilt from the Genesis artifacts present under +# $tftpdir/xcat and removed when none are left. Returns the path written. +sub _write_grub2_discovery_config { + my (%args) = @_; + my $tftpdir = $args{tftpdir}; + my $hexnet = uc($args{hexnet}); + my $cfgpath = "$tftpdir/boot/grub2/grub.cfg-$hexnet"; + my @arches = _grub2_discovery_arches($tftpdir); + unless (@arches) { + unlink($cfgpath); + return; + } + my $cmdline = "xcatd=$args{xcatd_address}:$args{xcatdport}"; + if (defined($args{consolecmdline}) and $args{consolecmdline} ne '') { + $cmdline .= " $args{consolecmdline}"; + } + # TFTP hands the large Genesis image to one client at a time, so the default entry + # loads it over HTTP like netboot=grub2-http; the TFTP entry is for a management + # node that does not serve the TFTP root over HTTP. + my $httpport = $args{httpport} || '80'; + my $httproot = 'http,' . $args{xcatd_address} . ($httpport eq '80' ? '' : ":$httpport"); + my $http_tftp_root = '/tftpboot'; + my $tftproot = 'tftp,' . $args{xcatd_address}; + my $content = "# xCAT Genesis discovery for network $hexnet - generated by mknb, do not edit\n"; + $content .= "set default=0\n"; + # Every architecture branch defines the HTTP entry first and the TFTP entry + # second, so a payload GRUB cannot fetch over HTTP is retried over TFTP. + $content .= "set fallback=1\n"; + $content .= "set timeout=5\n"; + my $keyword = 'if'; + foreach my $entry (@arches) { + my ($arch, $grub_cpu, $kernel, $initrd) = @{$entry}; + $content .= "$keyword [ \"\$grub_cpu\" = \"$grub_cpu\" ]; then\n"; + $content .= "menuentry \"xCAT Genesis $arch\" {\n"; + $content .= " insmod http\n"; + $content .= " insmod tftp\n"; + $content .= " set root=$httproot\n"; + $content .= " linux $http_tftp_root/$kernel $cmdline BOOTIF=\$net_default_mac\n"; + $content .= " initrd $http_tftp_root/$initrd\n"; + $content .= "}\n"; + $content .= "menuentry \"xCAT Genesis $arch (TFTP)\" {\n"; + $content .= " insmod tftp\n"; + $content .= " set root=$tftproot\n"; + $content .= " linux /$kernel $cmdline BOOTIF=\$net_default_mac\n"; + $content .= " initrd /$initrd\n"; + $content .= "}\n"; + $keyword = 'elif'; + } + $content .= "fi\n"; + # nodeset hard-links grub.cfg-<8 hex digit ip> to the node file; on a /32 network that is + # this file's name, so replace the name instead of truncating a shared inode. + unlink($cfgpath); + open(my $cfg, ">", $cfgpath) or return; + print $cfg $content; + close($cfg); + chmod(0644, $cfgpath); + return $cfgpath; +} + 1; diff --git a/xCAT-server/lib/xcat/plugins/nodediscover.pm b/xCAT-server/lib/xcat/plugins/nodediscover.pm index d7f4fef1e..8e9b77fb0 100644 --- a/xCAT-server/lib/xcat/plugins/nodediscover.pm +++ b/xCAT-server/lib/xcat/plugins/nodediscover.pm @@ -137,6 +137,31 @@ sub handled_commands { }; } +# Pick the noderes.netboot method a freshly discovered node should use when +# the admin has not already chosen one that fits its architecture. Returns the +# method to set, or undef to leave noderes.netboot alone. +sub _default_netboot { + my ($arch, $platform, $currboot) = @_; + $arch = '' unless defined $arch; + $platform = '' unless defined $platform; + $currboot = '' unless defined $currboot; + + if ($arch =~ /x86/ and $currboot !~ /pxe/ and $currboot !~ /xnba/) { + return 'xnba'; + } elsif ($arch =~ /ppc/ and $platform =~ /PowerNV/) { + return 'petitboot'; + } elsif ($arch =~ /ppc/ and $currboot !~ /yaboot/) { + return 'yaboot'; + } elsif ($arch =~ /armv7l/ and $currboot !~ /onie/) { + #for onie switch, the netboot should be "onie" + return 'onie'; + } elsif ($arch =~ /^riscv64$/ and $currboot !~ /grub2/) { + #riscv64 nodes boot through UEFI and grub2 only + return 'grub2'; + } + return; +} + sub process_request { my $request = shift; my $callback = shift; @@ -230,15 +255,12 @@ sub process_request { $currboot = $rent->{'netboot'}; } - if ($request->{arch}->[0] =~ /x86/ and $currboot !~ /pxe/ and $currboot !~ /xnba/) { - $nrtab->setNodeAttribs($node, { netboot => 'xnba' }); - } elsif ($request->{arch}->[0] =~ /ppc/ and $request->{platform}->[0] =~ /PowerNV/) { - $nrtab->setNodeAttribs($node, { netboot => 'petitboot' }); - } elsif ($request->{arch}->[0] =~ /ppc/ and $currboot !~ /yaboot/) { - $nrtab->setNodeAttribs($node, { netboot => 'yaboot' }); - } elsif($request->{arch}->[0] =~ /armv7l/ and $currboot !~ /onie/) { - #for onie switch, the netboot should be "onie" - $nrtab->setNodeAttribs($node, { netboot => 'onie' }); + # do not dereference platform unless the request carries it: the key would be + # autovivified into the request hash and later stored as discovery data + my $platform = exists $request->{platform} ? $request->{platform}->[0] : undef; + my $netboot = _default_netboot($request->{arch}->[0], $platform, $currboot); + if (defined $netboot) { + $nrtab->setNodeAttribs($node, { netboot => $netboot }); } } diff --git a/xCAT-server/share/xcat/install/rh/compute.rhels10.riscv64.pkglist b/xCAT-server/share/xcat/install/rh/compute.rhels10.riscv64.pkglist new file mode 100644 index 000000000..437df1d02 --- /dev/null +++ b/xCAT-server/share/xcat/install/rh/compute.rhels10.riscv64.pkglist @@ -0,0 +1,16 @@ +@Minimal Install +kernel +chrony +net-tools +nfs-utils +openssh-server +openssl +rsync +util-linux +wget +python3 +tar +bzip2 +perl-interpreter +grub2-efi-riscv64 +efibootmgr diff --git a/xCAT-server/share/xcat/install/rh/compute.rhels10.riscv64.tmpl b/xCAT-server/share/xcat/install/rh/compute.rhels10.riscv64.tmpl new file mode 100644 index 000000000..dbf34f13c --- /dev/null +++ b/xCAT-server/share/xcat/install/rh/compute.rhels10.riscv64.tmpl @@ -0,0 +1,79 @@ +# Use text install +text +# Use network installation +%include /tmp/repos +# Keyboard layouts +keyboard --vckeymap=us --xlayouts='us' +# System language +lang en_US.UTF-8 + +# Network information +#KICKSTARTNET# +# Root password +rootpw --iscrypted #CRYPT:passwd:key=system,username=root:password# +# Not run the Setup Agent on first boot +firstboot --disable +# Do not configure the X Window System +skipx +# System services +#services --enabled="chronyd" +# System timezone +timezone #TABLE:site:key=timezone:value# --utc +# Partition clearing information +zerombr +clearpart --all --initlabel +#XCAT_PARTITION_START# +%include /tmp/partitionfile +#XCAT_PARTITION_END# + +# Do not configure any iptables rules +firewall --disable +selinux --disable +reboot + +# riscv64: EL10 has no default crash kernel reservation for this architecture +# (kdumpctl get-default-crashkernel is empty), so the installer's kdump add-on +# would write the literal "crashkernel=auto", which the kernel ignores: nothing +# is reserved and kdump.service fails on every node. Leave kdump off here; a +# node that should take crash dumps gets a real reservation through +# linuximage.addkcmdline or bootparams.addkcmdline (crashkernel=). +%addon com_redhat_kdump --disable +%end + +# riscv64: the EL10 anaconda has no RISC-V EFI platform and always asks for the +# x86 UEFI boot loader packages (grub2-efi-x64, shim-x64), which do not exist +# for riscv64; tolerate that. The riscv64 package list pulls grub2-efi-riscv64 +# and efibootmgr in explicitly. +%packages --ignoremissing +#INCLUDE_DEFAULT_PKGLIST# + +%end + +%pre +{ +echo "Running Kickstart Pre-installation script..." +#INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/pre.rhels10# +} &>>/tmp/pre-install.log +%end +%post --interpreter=/bin/bash +mkdir -p /var/log/xcat/ +cat /tmp/pre-install.log >>/var/log/xcat/xcat.log +{ +echo "Running Kickstart Post-installation script..." +#INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/post.xcat.rhels10# +#INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/post.rhels10.riscv64# +#INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/post.rhels10# +} &>>/var/log/xcat/xcat.log +%end + +# --ignoremissing above also hides a missing grub2-efi-riscv64, which would leave the +# node with no boot loader at all. Fail the installation instead of rebooting into +# firmware that has nothing to load. +%post --erroronfail --interpreter=/bin/bash +if [ ! -f /boot/efi/EFI/BOOT/BOOTRISCV64.EFI ] && + ! ls /boot/efi/EFI/*/grubriscv64.efi >/dev/null 2>&1; then + echo "riscv64: no grub2 UEFI boot loader on the ESP; grub2-efi-riscv64 is missing" \ + >>/var/log/xcat/xcat.log + exit 1 +fi +%end diff --git a/xCAT-server/share/xcat/install/rh/service.rhels10.riscv64.otherpkgs.pkglist b/xCAT-server/share/xcat/install/rh/service.rhels10.riscv64.otherpkgs.pkglist new file mode 100644 index 000000000..cfcb8349f --- /dev/null +++ b/xCAT-server/share/xcat/install/rh/service.rhels10.riscv64.otherpkgs.pkglist @@ -0,0 +1,2 @@ +xcat/xcat-core/xCATsn +xcat/xcat-dep/rh10/riscv64/goconserver diff --git a/xCAT-server/share/xcat/install/rh/service.rhels10.riscv64.pkglist b/xCAT-server/share/xcat/install/rh/service.rhels10.riscv64.pkglist new file mode 100644 index 000000000..eb3b4ec36 --- /dev/null +++ b/xCAT-server/share/xcat/install/rh/service.rhels10.riscv64.pkglist @@ -0,0 +1,21 @@ +@Minimal Install +kernel +chrony +net-tools +nfs-utils +openssh-server +openssl +rsync +util-linux +wget +mariadb-connector-odbc +perl-DBD-MySQL +perl-DBD-Pg +unixODBC +python3 +tar +bzip2 +perl-interpreter +perl-lib +grub2-efi-riscv64 +efibootmgr diff --git a/xCAT-server/share/xcat/install/rh/service.rhels10.riscv64.tmpl b/xCAT-server/share/xcat/install/rh/service.rhels10.riscv64.tmpl new file mode 100644 index 000000000..dbf34f13c --- /dev/null +++ b/xCAT-server/share/xcat/install/rh/service.rhels10.riscv64.tmpl @@ -0,0 +1,79 @@ +# Use text install +text +# Use network installation +%include /tmp/repos +# Keyboard layouts +keyboard --vckeymap=us --xlayouts='us' +# System language +lang en_US.UTF-8 + +# Network information +#KICKSTARTNET# +# Root password +rootpw --iscrypted #CRYPT:passwd:key=system,username=root:password# +# Not run the Setup Agent on first boot +firstboot --disable +# Do not configure the X Window System +skipx +# System services +#services --enabled="chronyd" +# System timezone +timezone #TABLE:site:key=timezone:value# --utc +# Partition clearing information +zerombr +clearpart --all --initlabel +#XCAT_PARTITION_START# +%include /tmp/partitionfile +#XCAT_PARTITION_END# + +# Do not configure any iptables rules +firewall --disable +selinux --disable +reboot + +# riscv64: EL10 has no default crash kernel reservation for this architecture +# (kdumpctl get-default-crashkernel is empty), so the installer's kdump add-on +# would write the literal "crashkernel=auto", which the kernel ignores: nothing +# is reserved and kdump.service fails on every node. Leave kdump off here; a +# node that should take crash dumps gets a real reservation through +# linuximage.addkcmdline or bootparams.addkcmdline (crashkernel=). +%addon com_redhat_kdump --disable +%end + +# riscv64: the EL10 anaconda has no RISC-V EFI platform and always asks for the +# x86 UEFI boot loader packages (grub2-efi-x64, shim-x64), which do not exist +# for riscv64; tolerate that. The riscv64 package list pulls grub2-efi-riscv64 +# and efibootmgr in explicitly. +%packages --ignoremissing +#INCLUDE_DEFAULT_PKGLIST# + +%end + +%pre +{ +echo "Running Kickstart Pre-installation script..." +#INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/pre.rhels10# +} &>>/tmp/pre-install.log +%end +%post --interpreter=/bin/bash +mkdir -p /var/log/xcat/ +cat /tmp/pre-install.log >>/var/log/xcat/xcat.log +{ +echo "Running Kickstart Post-installation script..." +#INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/post.xcat.rhels10# +#INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/post.rhels10.riscv64# +#INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/post.rhels10# +} &>>/var/log/xcat/xcat.log +%end + +# --ignoremissing above also hides a missing grub2-efi-riscv64, which would leave the +# node with no boot loader at all. Fail the installation instead of rebooting into +# firmware that has nothing to load. +%post --erroronfail --interpreter=/bin/bash +if [ ! -f /boot/efi/EFI/BOOT/BOOTRISCV64.EFI ] && + ! ls /boot/efi/EFI/*/grubriscv64.efi >/dev/null 2>&1; then + echo "riscv64: no grub2 UEFI boot loader on the ESP; grub2-efi-riscv64 is missing" \ + >>/var/log/xcat/xcat.log + exit 1 +fi +%end diff --git a/xCAT-server/share/xcat/install/rocky/compute.rocky10.riscv64.pkglist b/xCAT-server/share/xcat/install/rocky/compute.rocky10.riscv64.pkglist new file mode 100644 index 000000000..437df1d02 --- /dev/null +++ b/xCAT-server/share/xcat/install/rocky/compute.rocky10.riscv64.pkglist @@ -0,0 +1,16 @@ +@Minimal Install +kernel +chrony +net-tools +nfs-utils +openssh-server +openssl +rsync +util-linux +wget +python3 +tar +bzip2 +perl-interpreter +grub2-efi-riscv64 +efibootmgr diff --git a/xCAT-server/share/xcat/install/rocky/compute.rocky10.riscv64.tmpl b/xCAT-server/share/xcat/install/rocky/compute.rocky10.riscv64.tmpl new file mode 100644 index 000000000..dbf34f13c --- /dev/null +++ b/xCAT-server/share/xcat/install/rocky/compute.rocky10.riscv64.tmpl @@ -0,0 +1,79 @@ +# Use text install +text +# Use network installation +%include /tmp/repos +# Keyboard layouts +keyboard --vckeymap=us --xlayouts='us' +# System language +lang en_US.UTF-8 + +# Network information +#KICKSTARTNET# +# Root password +rootpw --iscrypted #CRYPT:passwd:key=system,username=root:password# +# Not run the Setup Agent on first boot +firstboot --disable +# Do not configure the X Window System +skipx +# System services +#services --enabled="chronyd" +# System timezone +timezone #TABLE:site:key=timezone:value# --utc +# Partition clearing information +zerombr +clearpart --all --initlabel +#XCAT_PARTITION_START# +%include /tmp/partitionfile +#XCAT_PARTITION_END# + +# Do not configure any iptables rules +firewall --disable +selinux --disable +reboot + +# riscv64: EL10 has no default crash kernel reservation for this architecture +# (kdumpctl get-default-crashkernel is empty), so the installer's kdump add-on +# would write the literal "crashkernel=auto", which the kernel ignores: nothing +# is reserved and kdump.service fails on every node. Leave kdump off here; a +# node that should take crash dumps gets a real reservation through +# linuximage.addkcmdline or bootparams.addkcmdline (crashkernel=). +%addon com_redhat_kdump --disable +%end + +# riscv64: the EL10 anaconda has no RISC-V EFI platform and always asks for the +# x86 UEFI boot loader packages (grub2-efi-x64, shim-x64), which do not exist +# for riscv64; tolerate that. The riscv64 package list pulls grub2-efi-riscv64 +# and efibootmgr in explicitly. +%packages --ignoremissing +#INCLUDE_DEFAULT_PKGLIST# + +%end + +%pre +{ +echo "Running Kickstart Pre-installation script..." +#INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/pre.rhels10# +} &>>/tmp/pre-install.log +%end +%post --interpreter=/bin/bash +mkdir -p /var/log/xcat/ +cat /tmp/pre-install.log >>/var/log/xcat/xcat.log +{ +echo "Running Kickstart Post-installation script..." +#INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/post.xcat.rhels10# +#INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/post.rhels10.riscv64# +#INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/post.rhels10# +} &>>/var/log/xcat/xcat.log +%end + +# --ignoremissing above also hides a missing grub2-efi-riscv64, which would leave the +# node with no boot loader at all. Fail the installation instead of rebooting into +# firmware that has nothing to load. +%post --erroronfail --interpreter=/bin/bash +if [ ! -f /boot/efi/EFI/BOOT/BOOTRISCV64.EFI ] && + ! ls /boot/efi/EFI/*/grubriscv64.efi >/dev/null 2>&1; then + echo "riscv64: no grub2 UEFI boot loader on the ESP; grub2-efi-riscv64 is missing" \ + >>/var/log/xcat/xcat.log + exit 1 +fi +%end diff --git a/xCAT-server/share/xcat/install/rocky/service.rocky10.riscv64.otherpkgs.pkglist b/xCAT-server/share/xcat/install/rocky/service.rocky10.riscv64.otherpkgs.pkglist new file mode 100644 index 000000000..cfcb8349f --- /dev/null +++ b/xCAT-server/share/xcat/install/rocky/service.rocky10.riscv64.otherpkgs.pkglist @@ -0,0 +1,2 @@ +xcat/xcat-core/xCATsn +xcat/xcat-dep/rh10/riscv64/goconserver diff --git a/xCAT-server/share/xcat/install/rocky/service.rocky10.riscv64.pkglist b/xCAT-server/share/xcat/install/rocky/service.rocky10.riscv64.pkglist new file mode 100644 index 000000000..e9c3e2263 --- /dev/null +++ b/xCAT-server/share/xcat/install/rocky/service.rocky10.riscv64.pkglist @@ -0,0 +1,19 @@ +@^minimal-environment +chrony +net-tools +nfs-utils +openssh-server +rsync +util-linux +wget +mariadb-connector-odbc +perl-DBD-MySQL +perl-DBD-Pg +unixODBC +python3 +tar +bzip2 +perl-interpreter +perl-lib +grub2-efi-riscv64 +efibootmgr diff --git a/xCAT-server/share/xcat/install/rocky/service.rocky10.riscv64.tmpl b/xCAT-server/share/xcat/install/rocky/service.rocky10.riscv64.tmpl new file mode 100644 index 000000000..b7c6dc41e --- /dev/null +++ b/xCAT-server/share/xcat/install/rocky/service.rocky10.riscv64.tmpl @@ -0,0 +1,79 @@ +# Use text install +text +# Use network installation +%include /tmp/repos +# Keyboard layouts +keyboard --vckeymap=us --xlayouts='us' +# System language +lang en_US.UTF-8 + +# Network information +#KICKSTARTNET# +# Root password +rootpw --iscrypted #CRYPT:passwd:key=system,username=root:password# +# Not run the Setup Agent on first boot +firstboot --disable +# Do not configure the X Window System +skipx +# System services +#services --enabled="chronyd" +# System timezone +timezone #TABLE:site:key=timezone:value# --utc +# Partition clearing information +zerombr +clearpart --all --initlabel +#XCAT_PARTITION_START# +%include /tmp/partitionfile +#XCAT_PARTITION_END# + +# Do not configure any iptables rules +firewall --disable +selinux --disable +reboot + +# riscv64: EL10 has no default crash kernel reservation for this architecture +# (kdumpctl get-default-crashkernel is empty), so the installer's kdump add-on +# would write the literal "crashkernel=auto", which the kernel ignores: nothing +# is reserved and kdump.service fails on every node. Leave kdump off here; a +# node that should take crash dumps gets a real reservation through +# linuximage.addkcmdline or bootparams.addkcmdline (crashkernel=). +%addon com_redhat_kdump --disable +%end + +# riscv64: the EL10 anaconda has no RISC-V EFI platform and always asks for the +# x86 UEFI boot loader packages (grub2-efi-x64, shim-x64), which do not exist +# for riscv64; tolerate that. The riscv64 package list pulls grub2-efi-riscv64 +# and efibootmgr in explicitly. +%packages --ignoremissing +#INCLUDE_DEFAULT_PKGLIST# + +%end + +%pre +{ +echo "Running Kickstart Pre-installation script..." +#INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/pre.rhels8# +} &>>/tmp/pre-install.log +%end +%post --interpreter=/bin/bash +mkdir -p /var/log/xcat/ +cat /tmp/pre-install.log >>/var/log/xcat/xcat.log +{ +echo "Running Kickstart Post-installation script..." +#INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/post.xcat.ng# +#INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/post.rhels10.riscv64# +#INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/post.rhels8# +} &>>/var/log/xcat/xcat.log +%end + +# --ignoremissing above also hides a missing grub2-efi-riscv64, which would leave the +# node with no boot loader at all. Fail the installation instead of rebooting into +# firmware that has nothing to load. +%post --erroronfail --interpreter=/bin/bash +if [ ! -f /boot/efi/EFI/BOOT/BOOTRISCV64.EFI ] && + ! ls /boot/efi/EFI/*/grubriscv64.efi >/dev/null 2>&1; then + echo "riscv64: no grub2 UEFI boot loader on the ESP; grub2-efi-riscv64 is missing" \ + >>/var/log/xcat/xcat.log + exit 1 +fi +%end diff --git a/xCAT-server/share/xcat/install/scripts/post.rhels10.riscv64 b/xCAT-server/share/xcat/install/scripts/post.rhels10.riscv64 new file mode 100644 index 000000000..3ede6003f --- /dev/null +++ b/xCAT-server/share/xcat/install/scripts/post.rhels10.riscv64 @@ -0,0 +1,58 @@ +#!/bin/sh +# riscv64 UEFI boot entry fix-up for EL10 kickstart installs. +# +# The EL10 anaconda has no RISC-V EFI platform: it asks for the x86 boot +# loader packages (grub2-efi-x64, shim-x64; tolerated with --ignoremissing) +# and registers the UEFI boot entry as \EFI\\shimx64.efi, which does +# not exist on riscv64, so the firmware cannot boot the installed system. +# grub2-efi-riscv64 (from the riscv64 package list) provides +# \EFI\\grubriscv64.efi and anaconda still writes the ESP grub.cfg +# stub next to it, so point the boot entry at that image and also place the +# removable-media fallback loader \EFI\BOOT\BOOTRISCV64.EFI for firmware +# without usable NVRAM entries. Runs in the installed system chroot. +xcat_riscv_install_root=${XCAT_INSTALL_ROOT:-} +xcat_riscv_uname=${XCAT_UNAME:-uname} +xcat_riscv_efibootmgr=${XCAT_EFIBOOTMGR:-efibootmgr} +xcat_riscv_findmnt=${XCAT_FINDMNT:-findmnt} +xcat_riscv_lsblk=${XCAT_LSBLK:-lsblk} + +if [ "$("$xcat_riscv_uname" -m)" = "riscv64" ] && [ -d "$xcat_riscv_install_root/boot/efi/EFI" ]; then + grubefi="" + for candidate in "$xcat_riscv_install_root"/boot/efi/EFI/*/grubriscv64.efi; do + [ -f "$candidate" ] || continue + grubefi="$candidate" + break + done + if [ -n "$grubefi" ]; then + vendordir="$(basename "$(dirname "$grubefi")")" + mkdir -p "$xcat_riscv_install_root/boot/efi/EFI/BOOT" + cp -f "$grubefi" "$xcat_riscv_install_root/boot/efi/EFI/BOOT/BOOTRISCV64.EFI" + printf '%s\n' "riscv64: installed \\EFI\\BOOT\\BOOTRISCV64.EFI from \\EFI\\$vendordir\\grubriscv64.efi" + if command -v "$xcat_riscv_efibootmgr" >/dev/null 2>&1 && "$xcat_riscv_efibootmgr" >/dev/null 2>&1; then + espdev="$("$xcat_riscv_findmnt" -no SOURCE "$xcat_riscv_install_root/boot/efi" 2>/dev/null)" + # lsblk pads its output, and efibootmgr wants bare values + disk="$("$xcat_riscv_lsblk" -no PKNAME "$espdev" 2>/dev/null | head -1 | tr -d '[:space:]')" + part="$("$xcat_riscv_lsblk" -no PARTN "$espdev" 2>/dev/null | head -1 | tr -d '[:space:]')" + if [ -n "$disk" ] && [ -n "$part" ]; then + # drop the x86 entries anaconda created for this install, and any + # riscv64 entry a previous provisioning of this node left behind, so + # reinstalling does not fill the firmware variable store with copies + for bootnum in $("$xcat_riscv_efibootmgr" -v 2>/dev/null | awk '/\\EFI\\[^\\]+\\(shimx64|grubx64|grubriscv64)\.efi/ { sub(/^Boot/, "", $1); sub(/\*$/, "", $1); print $1 }'); do + "$xcat_riscv_efibootmgr" -q -b "$bootnum" -B || true + done + label="$(. "$xcat_riscv_install_root/etc/os-release" 2>/dev/null; echo "${NAME:-Linux}")" + if "$xcat_riscv_efibootmgr" -q -c -d "/dev/$disk" -p "$part" -L "$label" -l "\\EFI\\$vendordir\\grubriscv64.efi"; then + printf '%s\n' "riscv64: UEFI boot entry \"$label\" -> \\EFI\\$vendordir\\grubriscv64.efi on /dev/$disk part $part" + else + printf '%s\n' "riscv64: efibootmgr could not create the boot entry; the firmware will use \\EFI\\BOOT\\BOOTRISCV64.EFI" + fi + else + printf '%s\n' "riscv64: could not determine the ESP disk/partition; the firmware will use \\EFI\\BOOT\\BOOTRISCV64.EFI" + fi + else + printf '%s\n' "riscv64: efibootmgr unavailable; the firmware will use \\EFI\\BOOT\\BOOTRISCV64.EFI" + fi + else + printf '%s\n' "riscv64: no \\EFI\\*\\grubriscv64.efi on the ESP; is grub2-efi-riscv64 in the package list?" + fi +fi diff --git a/xCAT-server/share/xcat/netboot/imgutils/imgutils.pm b/xCAT-server/share/xcat/netboot/imgutils/imgutils.pm index ea01a75ff..644a6e671 100644 --- a/xCAT-server/share/xcat/netboot/imgutils/imgutils.pm +++ b/xCAT-server/share/xcat/netboot/imgutils/imgutils.pm @@ -79,6 +79,13 @@ sub _profile_lookup_osbase_list { return grep { $_ ne $osver } xCAT::SvrUtils::get_os_search_list($osver); } +sub resolver_library_paths { + my $arch = shift; + my $libdir = $arch =~ /x86_64|aarch64|riscv64/ ? 'lib64' : 'lib'; + + return ( "$libdir/libnss_dns.so.2", "$libdir/libresolv.so.2" ); +} + sub get_profile_def_filename { my $osver = shift; my $profile = shift; @@ -256,6 +263,7 @@ sub default_net_drivers { x86 => [qw(tg3 bnx2 bnx2x e1000 e1000e igb mlx_en mlx5_core virtio_net be2net)], x86_64 => [qw(tg3 bnx2 bnx2x e1000 e1000e igb mlx_en mlx5_core virtio_net be2net)], aarch64 => [qw(tg3 bnx2 bnx2x e1000e igb mlx_en mlx5_core virtio_net)], + riscv64 => [qw(e1000 e1000e igb ixgbe r8169 tg3 bnx2x mlx5_core virtio_net)], ppc64 => [qw(e1000 e1000e igb ibmveth ehea)], s390x => [qw(qdio ccwgroup)], }, diff --git a/xCAT-server/share/xcat/netboot/rh/compute.rhels10.riscv64.exlist b/xCAT-server/share/xcat/netboot/rh/compute.rhels10.riscv64.exlist new file mode 100644 index 000000000..07436e39f --- /dev/null +++ b/xCAT-server/share/xcat/netboot/rh/compute.rhels10.riscv64.exlist @@ -0,0 +1,33 @@ +./boot* +./usr/include* +./usr/lib/locale* +./usr/lib64/perl5/Encode/CN* +./usr/lib64/perl5/Encode/JP* +./usr/lib64/perl5/Encode/TW* +./usr/lib64/perl5/Encode/KR* +./lib/kbd/keymaps/i386* +./lib/kbd/keymaps/mac* +./lib/kbd/keymaps/include* +./usr/local/include* +./usr/local/share/man* +./usr/share/man* +./usr/share/cracklib* +./usr/share/doc* +./usr/share/gnome* +./usr/share/i18n* ++./usr/share/i18n/en_US* +./usr/share/info* +./usr/share/locale/* ++./usr/share/locale/en_US* ++./usr/share/locale/C* ++./usr/share/locale/locale.alias ++./usr/lib/locale/locale-archive ++./usr/lib/locale/en* ++./usr/lib/locale/C* +./usr/share/omf* +./usr/share/vim/site/doc* +./usr/share/vim/vim74/doc* +./usr/share/zoneinfo* +./var/cache/man* +./var/lib/yum* +./tmp* diff --git a/xCAT-server/share/xcat/netboot/rh/compute.rhels10.riscv64.pkglist b/xCAT-server/share/xcat/netboot/rh/compute.rhels10.riscv64.pkglist new file mode 100644 index 000000000..ccc8bfd79 --- /dev/null +++ b/xCAT-server/share/xcat/netboot/rh/compute.rhels10.riscv64.pkglist @@ -0,0 +1,30 @@ +@minimal-environment +initscripts +chrony +kernel +net-tools +nfs-utils +openssh-server +rsync +tar +util-linux +wget +python3 +tar +bzip2 +bc +dracut +dracut-network +NetworkManager +rsyslog +hostname +e2fsprogs +ethtool +parted +openssl +openssh-clients +bash +vim-minimal +rpm +iputils +perl-interpreter diff --git a/xCAT-server/share/xcat/netboot/rh/compute.rhels10.riscv64.postinstall b/xCAT-server/share/xcat/netboot/rh/compute.rhels10.riscv64.postinstall new file mode 100755 index 000000000..015cfec0f --- /dev/null +++ b/xCAT-server/share/xcat/netboot/rh/compute.rhels10.riscv64.postinstall @@ -0,0 +1,62 @@ +#!/bin/sh +#-- Do not remove following line if you want to make use of CVS version tracking +#-- $Id: compute.postinstall,v 1.21 2008/09/04 12:05:45 sikorsky Exp $ +#-- jurij.sikorsky@t-systems.cz +#-- +#-- this script is run after all packages from $profile.pkglist are installed +#-- +#-- it gets these arguments: +#-- +#-- $1 = install root (chroot directory for profile) +#-- $2 = OS version +#-- $3 = architecture +#-- $4 = profile name +#-- $5 = work dir (where genimage is located) +#-- +#-- +installroot=$1 +osver=$2 +arch=$3 +profile=$4 +workdir=$5 + +#-- Example how /etc/fstab can be automatically generated during image generation: +cat <$installroot/etc/fstab +proc /proc proc rw 0 0 +sysfs /sys sysfs rw 0 0 +devpts /dev/pts devpts rw,gid=5,mode=620 0 0 +END + +#-- Uncomment the line contains "cons" in /etc/inittab +#cons:12345:respawn:/sbin/smart_agetty -L 38400 console +#echo "co:2345:respawn:/sbin/agetty -L 38400 console" >> $installroot/etc/inittab + + +#-- Disable SELinux in the rootimg +#-- Redhat 7.3 will install selinux-policy and selinux is enabled by default +#-- Need to disable selinux, otherwise, the booting will hang on "Loading selinux policy" +if [ -f "$installroot/etc/selinux/config" ] +then + sed -i 's/SELINUX=enforcing\|permissive/SELINUX=disabled/' $installroot/etc/selinux/config +fi +#--for redhat 8 and 8.1 +#-- Need to disable firewalld, otherwise, the remoteshell script will not able to get all the SSH keys +FIREWALLD="$installroot/etc/systemd/system/multi-user.target.wants/firewalld.service" +if [[ -f "$FIREWALLD" || -L "$FIREWALLD" ]] +then + rm -f $FIREWALLD +fi +FIREWALLD1="$installroot/etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service" +if [[ -f "$FIREWALLD1" || -L "$FIREWALLD1" ]] +then + rm -f $FIREWALLD1 +fi + + +#-- Example of booted image versioning +#-- We want to know, with what configuration (version of the image) each node was booted. +#-- Hence, we keep image definition files and postscripts in CVS. During image generation we create file /etc/IMGVERSION and fill it with CVS "$Id$" of files with image definition (.pkglist, .exlist, .repolist, .postinstall). Then, during boot, each "CVS enabled" postscript (see /install/postscripts/cvs_template.sh and /install/postscripts/cvs_template.pl) adds one line to /etc/IMGVERSION. Then you can determine in any time what image you are running and what postscipts in which versions were run. +#cat /dev/null > $installroot/etc/IMGVERSION +#for ext in pkglist exlist postinstall repolist; do +# [ -r $workdir/$profile.$ext ] && cat $workdir/$profile.$ext | grep -E '^[[:space:]]*#.*[[:space:]]\$Id' >> $installroot/etc/IMGVERSION +#done diff --git a/xCAT-server/share/xcat/netboot/rh/genimage b/xCAT-server/share/xcat/netboot/rh/genimage index 20424cd82..452a4e3a9 100755 --- a/xCAT-server/share/xcat/netboot/rh/genimage +++ b/xCAT-server/share/xcat/netboot/rh/genimage @@ -1924,13 +1924,7 @@ EOMS } } - if ($arch =~ /x86_64/ or $arch =~ /aarch64/) { - push @filestoadd, "lib64/libnss_dns.so.2"; - push @filestoadd, "lib64/libresolv.so.2"; - } else { - push @filestoadd, "lib/libnss_dns.so.2"; - push @filestoadd, "lib/libresolv.so.2"; - } + push @filestoadd, imgutils::resolver_library_paths($arch); push @filestoadd, keys %libhash; find(\&isnetdriver, <$rootimg_dir/lib/modules/$kernelver/*>); diff --git a/xCAT-server/share/xcat/netboot/rh/service.rhels10.riscv64.exlist b/xCAT-server/share/xcat/netboot/rh/service.rhels10.riscv64.exlist new file mode 100644 index 000000000..07436e39f --- /dev/null +++ b/xCAT-server/share/xcat/netboot/rh/service.rhels10.riscv64.exlist @@ -0,0 +1,33 @@ +./boot* +./usr/include* +./usr/lib/locale* +./usr/lib64/perl5/Encode/CN* +./usr/lib64/perl5/Encode/JP* +./usr/lib64/perl5/Encode/TW* +./usr/lib64/perl5/Encode/KR* +./lib/kbd/keymaps/i386* +./lib/kbd/keymaps/mac* +./lib/kbd/keymaps/include* +./usr/local/include* +./usr/local/share/man* +./usr/share/man* +./usr/share/cracklib* +./usr/share/doc* +./usr/share/gnome* +./usr/share/i18n* ++./usr/share/i18n/en_US* +./usr/share/info* +./usr/share/locale/* ++./usr/share/locale/en_US* ++./usr/share/locale/C* ++./usr/share/locale/locale.alias ++./usr/lib/locale/locale-archive ++./usr/lib/locale/en* ++./usr/lib/locale/C* +./usr/share/omf* +./usr/share/vim/site/doc* +./usr/share/vim/vim74/doc* +./usr/share/zoneinfo* +./var/cache/man* +./var/lib/yum* +./tmp* diff --git a/xCAT-server/share/xcat/netboot/rh/service.rhels10.riscv64.otherpkgs.pkglist b/xCAT-server/share/xcat/netboot/rh/service.rhels10.riscv64.otherpkgs.pkglist new file mode 100644 index 000000000..cfcb8349f --- /dev/null +++ b/xCAT-server/share/xcat/netboot/rh/service.rhels10.riscv64.otherpkgs.pkglist @@ -0,0 +1,2 @@ +xcat/xcat-core/xCATsn +xcat/xcat-dep/rh10/riscv64/goconserver diff --git a/xCAT-server/share/xcat/netboot/rh/service.rhels10.riscv64.pkglist b/xCAT-server/share/xcat/netboot/rh/service.rhels10.riscv64.pkglist new file mode 100644 index 000000000..ac608b148 --- /dev/null +++ b/xCAT-server/share/xcat/netboot/rh/service.rhels10.riscv64.pkglist @@ -0,0 +1,30 @@ +@minimal-environment +chrony +kernel +net-tools +nfs-utils +openssh-server +rsync +tar +util-linux +wget +perl-DBD-MySQL +perl-DBD-Pg +perl-lib +python3 +tar +bzip2 +bc +dracut +dracut-network +rsyslog +hostname +e2fsprogs +ethtool +parted +openssl +openssh-clients +bash +vim-minimal +rpm +iputils diff --git a/xCAT-server/share/xcat/netboot/rh/service.rhels10.riscv64.postinstall b/xCAT-server/share/xcat/netboot/rh/service.rhels10.riscv64.postinstall new file mode 100755 index 000000000..015cfec0f --- /dev/null +++ b/xCAT-server/share/xcat/netboot/rh/service.rhels10.riscv64.postinstall @@ -0,0 +1,62 @@ +#!/bin/sh +#-- Do not remove following line if you want to make use of CVS version tracking +#-- $Id: compute.postinstall,v 1.21 2008/09/04 12:05:45 sikorsky Exp $ +#-- jurij.sikorsky@t-systems.cz +#-- +#-- this script is run after all packages from $profile.pkglist are installed +#-- +#-- it gets these arguments: +#-- +#-- $1 = install root (chroot directory for profile) +#-- $2 = OS version +#-- $3 = architecture +#-- $4 = profile name +#-- $5 = work dir (where genimage is located) +#-- +#-- +installroot=$1 +osver=$2 +arch=$3 +profile=$4 +workdir=$5 + +#-- Example how /etc/fstab can be automatically generated during image generation: +cat <$installroot/etc/fstab +proc /proc proc rw 0 0 +sysfs /sys sysfs rw 0 0 +devpts /dev/pts devpts rw,gid=5,mode=620 0 0 +END + +#-- Uncomment the line contains "cons" in /etc/inittab +#cons:12345:respawn:/sbin/smart_agetty -L 38400 console +#echo "co:2345:respawn:/sbin/agetty -L 38400 console" >> $installroot/etc/inittab + + +#-- Disable SELinux in the rootimg +#-- Redhat 7.3 will install selinux-policy and selinux is enabled by default +#-- Need to disable selinux, otherwise, the booting will hang on "Loading selinux policy" +if [ -f "$installroot/etc/selinux/config" ] +then + sed -i 's/SELINUX=enforcing\|permissive/SELINUX=disabled/' $installroot/etc/selinux/config +fi +#--for redhat 8 and 8.1 +#-- Need to disable firewalld, otherwise, the remoteshell script will not able to get all the SSH keys +FIREWALLD="$installroot/etc/systemd/system/multi-user.target.wants/firewalld.service" +if [[ -f "$FIREWALLD" || -L "$FIREWALLD" ]] +then + rm -f $FIREWALLD +fi +FIREWALLD1="$installroot/etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service" +if [[ -f "$FIREWALLD1" || -L "$FIREWALLD1" ]] +then + rm -f $FIREWALLD1 +fi + + +#-- Example of booted image versioning +#-- We want to know, with what configuration (version of the image) each node was booted. +#-- Hence, we keep image definition files and postscripts in CVS. During image generation we create file /etc/IMGVERSION and fill it with CVS "$Id$" of files with image definition (.pkglist, .exlist, .repolist, .postinstall). Then, during boot, each "CVS enabled" postscript (see /install/postscripts/cvs_template.sh and /install/postscripts/cvs_template.pl) adds one line to /etc/IMGVERSION. Then you can determine in any time what image you are running and what postscipts in which versions were run. +#cat /dev/null > $installroot/etc/IMGVERSION +#for ext in pkglist exlist postinstall repolist; do +# [ -r $workdir/$profile.$ext ] && cat $workdir/$profile.$ext | grep -E '^[[:space:]]*#.*[[:space:]]\$Id' >> $installroot/etc/IMGVERSION +#done diff --git a/xCAT-server/share/xcat/netboot/rocky/compute.rocky10.riscv64.exlist b/xCAT-server/share/xcat/netboot/rocky/compute.rocky10.riscv64.exlist new file mode 100644 index 000000000..07436e39f --- /dev/null +++ b/xCAT-server/share/xcat/netboot/rocky/compute.rocky10.riscv64.exlist @@ -0,0 +1,33 @@ +./boot* +./usr/include* +./usr/lib/locale* +./usr/lib64/perl5/Encode/CN* +./usr/lib64/perl5/Encode/JP* +./usr/lib64/perl5/Encode/TW* +./usr/lib64/perl5/Encode/KR* +./lib/kbd/keymaps/i386* +./lib/kbd/keymaps/mac* +./lib/kbd/keymaps/include* +./usr/local/include* +./usr/local/share/man* +./usr/share/man* +./usr/share/cracklib* +./usr/share/doc* +./usr/share/gnome* +./usr/share/i18n* ++./usr/share/i18n/en_US* +./usr/share/info* +./usr/share/locale/* ++./usr/share/locale/en_US* ++./usr/share/locale/C* ++./usr/share/locale/locale.alias ++./usr/lib/locale/locale-archive ++./usr/lib/locale/en* ++./usr/lib/locale/C* +./usr/share/omf* +./usr/share/vim/site/doc* +./usr/share/vim/vim74/doc* +./usr/share/zoneinfo* +./var/cache/man* +./var/lib/yum* +./tmp* diff --git a/xCAT-server/share/xcat/netboot/rocky/compute.rocky10.riscv64.pkglist b/xCAT-server/share/xcat/netboot/rocky/compute.rocky10.riscv64.pkglist new file mode 100644 index 000000000..ccc8bfd79 --- /dev/null +++ b/xCAT-server/share/xcat/netboot/rocky/compute.rocky10.riscv64.pkglist @@ -0,0 +1,30 @@ +@minimal-environment +initscripts +chrony +kernel +net-tools +nfs-utils +openssh-server +rsync +tar +util-linux +wget +python3 +tar +bzip2 +bc +dracut +dracut-network +NetworkManager +rsyslog +hostname +e2fsprogs +ethtool +parted +openssl +openssh-clients +bash +vim-minimal +rpm +iputils +perl-interpreter diff --git a/xCAT-server/share/xcat/netboot/rocky/compute.rocky10.riscv64.postinstall b/xCAT-server/share/xcat/netboot/rocky/compute.rocky10.riscv64.postinstall new file mode 100755 index 000000000..015cfec0f --- /dev/null +++ b/xCAT-server/share/xcat/netboot/rocky/compute.rocky10.riscv64.postinstall @@ -0,0 +1,62 @@ +#!/bin/sh +#-- Do not remove following line if you want to make use of CVS version tracking +#-- $Id: compute.postinstall,v 1.21 2008/09/04 12:05:45 sikorsky Exp $ +#-- jurij.sikorsky@t-systems.cz +#-- +#-- this script is run after all packages from $profile.pkglist are installed +#-- +#-- it gets these arguments: +#-- +#-- $1 = install root (chroot directory for profile) +#-- $2 = OS version +#-- $3 = architecture +#-- $4 = profile name +#-- $5 = work dir (where genimage is located) +#-- +#-- +installroot=$1 +osver=$2 +arch=$3 +profile=$4 +workdir=$5 + +#-- Example how /etc/fstab can be automatically generated during image generation: +cat <$installroot/etc/fstab +proc /proc proc rw 0 0 +sysfs /sys sysfs rw 0 0 +devpts /dev/pts devpts rw,gid=5,mode=620 0 0 +END + +#-- Uncomment the line contains "cons" in /etc/inittab +#cons:12345:respawn:/sbin/smart_agetty -L 38400 console +#echo "co:2345:respawn:/sbin/agetty -L 38400 console" >> $installroot/etc/inittab + + +#-- Disable SELinux in the rootimg +#-- Redhat 7.3 will install selinux-policy and selinux is enabled by default +#-- Need to disable selinux, otherwise, the booting will hang on "Loading selinux policy" +if [ -f "$installroot/etc/selinux/config" ] +then + sed -i 's/SELINUX=enforcing\|permissive/SELINUX=disabled/' $installroot/etc/selinux/config +fi +#--for redhat 8 and 8.1 +#-- Need to disable firewalld, otherwise, the remoteshell script will not able to get all the SSH keys +FIREWALLD="$installroot/etc/systemd/system/multi-user.target.wants/firewalld.service" +if [[ -f "$FIREWALLD" || -L "$FIREWALLD" ]] +then + rm -f $FIREWALLD +fi +FIREWALLD1="$installroot/etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service" +if [[ -f "$FIREWALLD1" || -L "$FIREWALLD1" ]] +then + rm -f $FIREWALLD1 +fi + + +#-- Example of booted image versioning +#-- We want to know, with what configuration (version of the image) each node was booted. +#-- Hence, we keep image definition files and postscripts in CVS. During image generation we create file /etc/IMGVERSION and fill it with CVS "$Id$" of files with image definition (.pkglist, .exlist, .repolist, .postinstall). Then, during boot, each "CVS enabled" postscript (see /install/postscripts/cvs_template.sh and /install/postscripts/cvs_template.pl) adds one line to /etc/IMGVERSION. Then you can determine in any time what image you are running and what postscipts in which versions were run. +#cat /dev/null > $installroot/etc/IMGVERSION +#for ext in pkglist exlist postinstall repolist; do +# [ -r $workdir/$profile.$ext ] && cat $workdir/$profile.$ext | grep -E '^[[:space:]]*#.*[[:space:]]\$Id' >> $installroot/etc/IMGVERSION +#done diff --git a/xCAT-server/share/xcat/netboot/rocky/service.rocky10.riscv64.exlist b/xCAT-server/share/xcat/netboot/rocky/service.rocky10.riscv64.exlist new file mode 100644 index 000000000..07436e39f --- /dev/null +++ b/xCAT-server/share/xcat/netboot/rocky/service.rocky10.riscv64.exlist @@ -0,0 +1,33 @@ +./boot* +./usr/include* +./usr/lib/locale* +./usr/lib64/perl5/Encode/CN* +./usr/lib64/perl5/Encode/JP* +./usr/lib64/perl5/Encode/TW* +./usr/lib64/perl5/Encode/KR* +./lib/kbd/keymaps/i386* +./lib/kbd/keymaps/mac* +./lib/kbd/keymaps/include* +./usr/local/include* +./usr/local/share/man* +./usr/share/man* +./usr/share/cracklib* +./usr/share/doc* +./usr/share/gnome* +./usr/share/i18n* ++./usr/share/i18n/en_US* +./usr/share/info* +./usr/share/locale/* ++./usr/share/locale/en_US* ++./usr/share/locale/C* ++./usr/share/locale/locale.alias ++./usr/lib/locale/locale-archive ++./usr/lib/locale/en* ++./usr/lib/locale/C* +./usr/share/omf* +./usr/share/vim/site/doc* +./usr/share/vim/vim74/doc* +./usr/share/zoneinfo* +./var/cache/man* +./var/lib/yum* +./tmp* diff --git a/xCAT-server/share/xcat/netboot/rocky/service.rocky10.riscv64.otherpkgs.pkglist b/xCAT-server/share/xcat/netboot/rocky/service.rocky10.riscv64.otherpkgs.pkglist new file mode 100644 index 000000000..cfcb8349f --- /dev/null +++ b/xCAT-server/share/xcat/netboot/rocky/service.rocky10.riscv64.otherpkgs.pkglist @@ -0,0 +1,2 @@ +xcat/xcat-core/xCATsn +xcat/xcat-dep/rh10/riscv64/goconserver diff --git a/xCAT-server/share/xcat/netboot/rocky/service.rocky10.riscv64.pkglist b/xCAT-server/share/xcat/netboot/rocky/service.rocky10.riscv64.pkglist new file mode 100644 index 000000000..ac608b148 --- /dev/null +++ b/xCAT-server/share/xcat/netboot/rocky/service.rocky10.riscv64.pkglist @@ -0,0 +1,30 @@ +@minimal-environment +chrony +kernel +net-tools +nfs-utils +openssh-server +rsync +tar +util-linux +wget +perl-DBD-MySQL +perl-DBD-Pg +perl-lib +python3 +tar +bzip2 +bc +dracut +dracut-network +rsyslog +hostname +e2fsprogs +ethtool +parted +openssl +openssh-clients +bash +vim-minimal +rpm +iputils diff --git a/xCAT-server/share/xcat/netboot/rocky/service.rocky10.riscv64.postinstall b/xCAT-server/share/xcat/netboot/rocky/service.rocky10.riscv64.postinstall new file mode 100755 index 000000000..015cfec0f --- /dev/null +++ b/xCAT-server/share/xcat/netboot/rocky/service.rocky10.riscv64.postinstall @@ -0,0 +1,62 @@ +#!/bin/sh +#-- Do not remove following line if you want to make use of CVS version tracking +#-- $Id: compute.postinstall,v 1.21 2008/09/04 12:05:45 sikorsky Exp $ +#-- jurij.sikorsky@t-systems.cz +#-- +#-- this script is run after all packages from $profile.pkglist are installed +#-- +#-- it gets these arguments: +#-- +#-- $1 = install root (chroot directory for profile) +#-- $2 = OS version +#-- $3 = architecture +#-- $4 = profile name +#-- $5 = work dir (where genimage is located) +#-- +#-- +installroot=$1 +osver=$2 +arch=$3 +profile=$4 +workdir=$5 + +#-- Example how /etc/fstab can be automatically generated during image generation: +cat <$installroot/etc/fstab +proc /proc proc rw 0 0 +sysfs /sys sysfs rw 0 0 +devpts /dev/pts devpts rw,gid=5,mode=620 0 0 +END + +#-- Uncomment the line contains "cons" in /etc/inittab +#cons:12345:respawn:/sbin/smart_agetty -L 38400 console +#echo "co:2345:respawn:/sbin/agetty -L 38400 console" >> $installroot/etc/inittab + + +#-- Disable SELinux in the rootimg +#-- Redhat 7.3 will install selinux-policy and selinux is enabled by default +#-- Need to disable selinux, otherwise, the booting will hang on "Loading selinux policy" +if [ -f "$installroot/etc/selinux/config" ] +then + sed -i 's/SELINUX=enforcing\|permissive/SELINUX=disabled/' $installroot/etc/selinux/config +fi +#--for redhat 8 and 8.1 +#-- Need to disable firewalld, otherwise, the remoteshell script will not able to get all the SSH keys +FIREWALLD="$installroot/etc/systemd/system/multi-user.target.wants/firewalld.service" +if [[ -f "$FIREWALLD" || -L "$FIREWALLD" ]] +then + rm -f $FIREWALLD +fi +FIREWALLD1="$installroot/etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service" +if [[ -f "$FIREWALLD1" || -L "$FIREWALLD1" ]] +then + rm -f $FIREWALLD1 +fi + + +#-- Example of booted image versioning +#-- We want to know, with what configuration (version of the image) each node was booted. +#-- Hence, we keep image definition files and postscripts in CVS. During image generation we create file /etc/IMGVERSION and fill it with CVS "$Id$" of files with image definition (.pkglist, .exlist, .repolist, .postinstall). Then, during boot, each "CVS enabled" postscript (see /install/postscripts/cvs_template.sh and /install/postscripts/cvs_template.pl) adds one line to /etc/IMGVERSION. Then you can determine in any time what image you are running and what postscipts in which versions were run. +#cat /dev/null > $installroot/etc/IMGVERSION +#for ext in pkglist exlist postinstall repolist; do +# [ -r $workdir/$profile.$ext ] && cat $workdir/$profile.$ext | grep -E '^[[:space:]]*#.*[[:space:]]\$Id' >> $installroot/etc/IMGVERSION +#done diff --git a/xCAT-server/xCAT-server.spec b/xCAT-server/xCAT-server.spec index 2c4f4b1a6..b964f90fd 100644 --- a/xCAT-server/xCAT-server.spec +++ b/xCAT-server/xCAT-server.spec @@ -51,7 +51,19 @@ Requires: perl-IO-Socket-SSL perl-XML-Simple perl-XML-Parser perl-Digest-SHA1 pe # therefore not auto-required, and (unlike SNMP, Expect, JSON, Net::Ping, # Time::HiRes and Text::Balanced) are not pulled in transitively by perl-xCAT # either. List them here or the affected subcommands die at load time. -Requires: perl-Net-Telnet perl-Net-DNS perl-Crypt-CBC perl-Crypt-Rijndael perl-DB_File +Requires: perl-Net-Telnet perl-Net-DNS perl-Crypt-CBC perl-Crypt-Rijndael +# DB_File is only used by the Confluent client (lib/xcat/Confluent/Client.pm). EL10 +# dropped libdb: EPEL still carries perl-DB_File for x86_64 and ppc64le, but riscv64 has +# no EPEL at all, so on EL10 ask for the module weakly -- it is installed where it exists +# and skipped where it cannot. The dependency generator still turns that "use DB_File" +# into a hard perl(DB_File) requirement; drop it there too, appending to any filter the +# build root already set. Every other platform keeps the hard requirement it has today. +%if 0%{?rhel} >= 10 +Recommends: perl-DB_File +%global __requires_exclude %{?__requires_exclude:%{__requires_exclude}|}^perl\\(DB_File\\)$ +%else +Requires: perl-DB_File +%endif %endif Obsoletes: atftp-xcat %endif diff --git a/xCAT-test/autotest/testcase/nodeset/cases1 b/xCAT-test/autotest/testcase/nodeset/cases1 index 8667e37ae..b3196702d 100644 --- a/xCAT-test/autotest/testcase/nodeset/cases1 +++ b/xCAT-test/autotest/testcase/nodeset/cases1 @@ -26,6 +26,13 @@ cmd:/opt/xcat/share/xcat/tools/autotest/testcase/nodeset/nodeset_osimage_grub2 check:rc==0 end +start:nodeset_osimage_grub2_riscv64 +label:others +description: Verify if `nodeset osimage` perform well for riscv64 nodes booted by grub2 over UEFI when part of nodes in a node range have problem +cmd:/opt/xcat/share/xcat/tools/autotest/testcase/nodeset/nodeset_osimage_grub2_riscv64 +check:rc==0 +end + start:nodeset_osimage_petitboot label:others description: Verify if `nodeset osimage` perform well when part of nodes in a node range have problem. Use petitboot OS loader diff --git a/xCAT-test/autotest/testcase/nodeset/functions.sh b/xCAT-test/autotest/testcase/nodeset/functions.sh index 5476a9411..fbd73fef0 100644 --- a/xCAT-test/autotest/testcase/nodeset/functions.sh +++ b/xCAT-test/autotest/testcase/nodeset/functions.sh @@ -118,6 +118,21 @@ function make_bogus_petitboot_nodes() done } +function make_bogus_riscv64_grub2_nodes() +{ + local i + # riscv64 boots through UEFI and grub2 + for i in {001..005} + do + mkdef -t node -o tz${i} \ + arch=riscv64 cons=ipmi groups=riscv64 mgt=ipmi \ + netboot=grub2 \ + ip=10.99.1.$((10#${i})) \ + mac=e6:d4:d2:3a:ad:0$((10#${i})) \ + profile=compute os=rhels10.99 + done +} + function make_bogus_xnba_nodes() { local i @@ -181,6 +196,16 @@ function make_bogus_x64_osimage() echo blah >/install/rhels6.99/x86_64/images/pxeboot/initrd.img } +function make_bogus_riscv64_osimage() +{ + mkdef "rhels10.99-riscv64-install-compute" \ + -u profile=compute provmethod=install \ + osvers=rhels10.99 osarch=riscv64 + mkdir -p /install/rhels10.99/riscv64/images/pxeboot + echo blah >/install/rhels10.99/riscv64/images/pxeboot/vmlinuz + echo blah >/install/rhels10.99/riscv64/images/pxeboot/initrd.img +} + function destory_bogus_osimages() { local o @@ -188,7 +213,8 @@ function destory_bogus_osimages() rhels7.99-ppc64le-install-compute \ rhels7.99-ppc64-install-compute \ rhels6.99-ppc64-install-compute \ - rhels6.99-x86_64-install-compute + rhels6.99-x86_64-install-compute \ + rhels10.99-riscv64-install-compute do rmdef -t osimage ${o} done diff --git a/xCAT-test/autotest/testcase/nodeset/nodeset_osimage_grub2_riscv64 b/xCAT-test/autotest/testcase/nodeset/nodeset_osimage_grub2_riscv64 new file mode 100755 index 000000000..2dc293615 --- /dev/null +++ b/xCAT-test/autotest/testcase/nodeset/nodeset_osimage_grub2_riscv64 @@ -0,0 +1,73 @@ +#!/bin/bash + +BASE_DIR="${0%/*}" + +! source "${BASE_DIR}/functions.sh" >/dev/null 2>&1 && + echo "File \"${BASE_DIR}/functions.sh\" not found" >&2 && exit 1 + +make_bogus_riscv64_osimage +make_bogus_riscv64_grub2_nodes + +# riscv64 nodes are booted by the grub2.riscv64 UEFI image; provide a bogus +# one when the management node does not ship it so nodeset can link to it +GRUB2_RISCV64="/tftpboot/boot/grub2/grub2.riscv64" +BOGUS_GRUB2_RISCV64="" +if [ ! -e "${GRUB2_RISCV64}" ] +then + mkdir -p "${GRUB2_RISCV64%/*}" + echo blah >"${GRUB2_RISCV64}" + BOGUS_GRUB2_RISCV64="${GRUB2_RISCV64}" +fi + +function custom_cleanup() +{ + destory_bogus_nodes + destory_bogus_osimages + [ -n "${BOGUS_GRUB2_RISCV64}" ] && rm -f "${BOGUS_GRUB2_RISCV64}" +} + +chdef tz002 mac= +chdef tz003 arch= + +NODESET_STDOUT="${TMP_DIR}/nodeset.out" +NODESET_STDERR="${TMP_DIR}/nodeset.err" + +nodeset tz001+4 osimage=rhels10.99-riscv64-install-compute \ + > >(tee "${NODESET_STDOUT}") 2> >(tee "${NODESET_STDERR}") +# Check $? != 0 +[ "$?" -ne "0" ] +exit_if_bad "$?" "nodeset command should exit with non-zero" + +grep 'tz002: .*o MAC address' "${NODESET_STDERR}" +exit_if_bad "$?" "nodeset command error message checking failed" + +# Make sure all other nodes are good +for node in tz001 tz004 tz005 +do + [ -f "/tftpboot/boot/grub2/${node}" ] + exit_if_bad "$?" "file not found /tftpboot/boot/grub2/${node}" + + # the per-node loader link points at the riscv64 grub2 image + [ "$(readlink "/tftpboot/boot/grub2/grub2-${node}")" = "grub2.riscv64" ] + exit_if_bad "$?" "grub2-${node} does not link to grub2.riscv64" + + # the installer kernel and initrd come from images/pxeboot + grep 'vmlinuz' "/tftpboot/boot/grub2/${node}" + exit_if_bad "$?" "installer kernel not found in /tftpboot/boot/grub2/${node}" + grep 'initrd.img' "/tftpboot/boot/grub2/${node}" + exit_if_bad "$?" "installer initrd not found in /tftpboot/boot/grub2/${node}" + + # the riscv64 grub2 image has no linuxefi command + ! grep 'linuxefi' "/tftpboot/boot/grub2/${node}" + exit_if_bad "$?" "linuxefi must not be used for riscv64 in /tftpboot/boot/grub2/${node}" +done + +# the hex ip and mac links nodeset writes shadow the discovery network files +[ -f "/tftpboot/boot/grub2/grub.cfg-0A630101" ] +exit_if_bad "$?" "file not found /tftpboot/boot/grub2/grub.cfg-0A630101" +[ -f "/tftpboot/boot/grub2/grub.cfg-01-e6-d4-d2-3a-ad-01" ] +exit_if_bad "$?" "file not found /tftpboot/boot/grub2/grub.cfg-01-e6-d4-d2-3a-ad-01" + +nodeset tz001+4 offline + +exit 0 diff --git a/xCAT-test/unit/copycd_grub2_loader.t b/xCAT-test/unit/copycd_grub2_loader.t new file mode 100644 index 000000000..f663beacc --- /dev/null +++ b/xCAT-test/unit/copycd_grub2_loader.t @@ -0,0 +1,97 @@ +#!/usr/bin/env perl +use strict; +use warnings; +no warnings 'once'; + +use File::Path qw(make_path); +use File::Slurper qw(read_text write_text); +use File::Spec; +use File::Temp qw(tempdir); +use FindBin; +use lib "$FindBin::Bin/../lib"; +use lib "$FindBin::Bin/../../perl-xCAT"; +use lib "$FindBin::Bin/../../xCAT-server/lib/perl"; +use Test::More; + +use XCAT::Test::File qw(repo_path); + +# copycd publishes the grub2 UEFI image of the installation media so that nodes +# of an architecture xCAT builds no boot loader for -- riscv64 -- can net boot +# without a further step. An image the management node already has is never +# replaced, and the media of every other architecture is left alone. + +my $plugin = repo_path('xCAT-server/lib/xcat/plugins/anaconda.pm'); +plan skip_all => "$plugin not found" unless -r $plugin; +$ENV{XCATROOT} ||= repo_path('xCAT-server'); +require $plugin; + +my $tftpdir; +no warnings 'redefine'; +local *xCAT::TableUtils::getTftpDir = sub { return $tftpdir; }; +use warnings; + +sub media { + my ( $root, $name, $image ) = @_; + my $path = File::Spec->catdir( $root, $name ); + make_path( File::Spec->catdir( $path, 'EFI', 'BOOT' ) ); + if ($image) { + my $file = File::Spec->catfile( $path, 'EFI', 'BOOT', $image ); + write_text( $file, "boot loader from the media\n" ); + } + return $path; +} + +sub messages { + my ($responses) = @_; + return join( ' ', map { ref($_) eq 'HASH' && $_->{data} ? "@{[ $_->{data} ]}" : () } @{$responses} ); +} + +my $root = tempdir( CLEANUP => 1 ); +$tftpdir = File::Spec->catdir( $root, 'tftpboot' ); +make_path($tftpdir); + +# riscv64 media, nothing published yet: the image is installed +my $riscv_media = media( $root, 'rocky10-riscv64', 'grubriscv64.efi' ); +my @responses; +my $written = xCAT_plugin::anaconda::_install_media_grub2_loader( + $riscv_media, 'riscv64', sub { push @responses, @_; } +); +my $loader = File::Spec->catfile( $tftpdir, 'boot', 'grub2', 'grub2.riscv64' ); +is( $written, $loader, 'riscv64 media publish their grub2 image as the riscv64 boot loader' ); +ok( -f $loader, 'the boot loader is written under the TFTP root' ); +is( ( stat($loader) )[7], ( stat( File::Spec->catfile( $riscv_media, 'EFI', 'BOOT', 'grubriscv64.efi' ) ) )[7], + 'the published image is the one from the media' ); +like( messages( \@responses ), qr/\QInstalled $loader from the media\E/, 'the published boot loader is reported' ); + +# a boot loader that is already there is never replaced +write_text( $loader, "installed by grub2-xcat\n" ); +@responses = (); +is( scalar xCAT_plugin::anaconda::_install_media_grub2_loader( $riscv_media, 'riscv64', sub { push @responses, @_; } ), + undef, 'an existing boot loader is kept' ); +my $content = read_text($loader); +is( $content, "installed by grub2-xcat\n", 'the existing boot loader is left untouched' ); +is( messages( \@responses ), '', 'nothing is reported when there is nothing to do' ); +unlink($loader); + +# other architectures are not touched, even when their media carry an image. +# Start from an empty TFTP root so the check covers the directory as well. +$tftpdir = File::Spec->catdir( $root, 'tftpboot-x86' ); +make_path($tftpdir); +my $x86_media = media( $root, 'rocky10-x86_64', 'grubx64.efi' ); +@responses = (); +is( scalar xCAT_plugin::anaconda::_install_media_grub2_loader( $x86_media, 'x86_64', sub { push @responses, @_; } ), + undef, 'x86_64 media publish no boot loader' ); +ok( !-e File::Spec->catfile( $tftpdir, 'boot', 'grub2', 'grub2.x86_64' ), 'no x86_64 boot loader is written' ); +ok( !-d File::Spec->catdir( $tftpdir, 'boot' ), 'the grub2 directory is only created when there is an image to put in it' ); + +# riscv64 media without the image are a no-op rather than an error +$tftpdir = File::Spec->catdir( $root, 'tftpboot-bare' ); +make_path($tftpdir); +my $bare_media = media( $root, 'rocky10-riscv64-bare' ); +@responses = (); +is( scalar xCAT_plugin::anaconda::_install_media_grub2_loader( $bare_media, 'riscv64', sub { push @responses, @_; } ), + undef, 'media without a grub2 image publish nothing' ); +is( messages( \@responses ), '', 'media without a grub2 image report nothing' ); +ok( !-d File::Spec->catdir( $tftpdir, 'boot' ), 'media without a grub2 image create no directories' ); + +done_testing(); diff --git a/xCAT-test/unit/dhcp_boot_policy.t b/xCAT-test/unit/dhcp_boot_policy.t index b5369b066..e06aafdda 100644 --- a/xCAT-test/unit/dhcp_boot_policy.t +++ b/xCAT-test/unit/dhcp_boot_policy.t @@ -9,13 +9,13 @@ use Test::More; use xCAT::DHCP::BootPolicy; my $fallback_classes = xCAT::DHCP::BootPolicy->kea_client_classes(); -is( scalar @$fallback_classes, 4, 'Kea boot policy omits xNBA classes when xNBA loaders are unavailable' ); +is( scalar @$fallback_classes, 5, 'Kea boot policy omits xNBA classes when xNBA loaders are unavailable' ); my %fallback_by_name = map { $_->{name} => $_ } @$fallback_classes; is( $fallback_by_name{'xcat-bios'}{'boot-file-name'}, 'pxelinux.0', 'BIOS clients fall back to pxelinux.0 without xNBA loaders' ); ok( !exists $fallback_by_name{'xcat-xnba-bios'}, 'xNBA user-class is not advertised without xNBA kpxe' ); my $classes = xCAT::DHCP::BootPolicy->kea_client_classes(xnba_kpxe => 1, xnba_efi => 1); -is( scalar @$classes, 5, 'Kea boot policy renders expected xNBA client classes' ); +is( scalar @$classes, 6, 'Kea boot policy renders expected xNBA client classes' ); my %by_name = map { $_->{name} => $_ } @$classes; is( $by_name{'xcat-bios'}{'boot-file-name'}, 'xcat/xnba.kpxe', 'BIOS clients receive xNBA kpxe' ); @@ -27,6 +27,10 @@ like( $by_name{'xcat-uefi-x64'}{test}, qr/not \(\(option\[77\]\.exists/, 'generi is( $by_name{'xcat-aarch64'}{'boot-file-name'}, 'boot/grub2/grub2.aarch64', 'AArch64 clients receive grub2 boot file' ); is( $by_name{'xcat-ppc64'}{'boot-file-name'}, '/boot/grub2/grub2.ppc', 'POWER clients receive grub2 Open Firmware boot file' ); is( $by_name{'xcat-ppc64'}{test}, 'option[93].hex == 0x000c', 'POWER class keeps existing POWER architecture id' ); +is( $by_name{'xcat-riscv64'}{'boot-file-name'}, 'boot/grub2/grub2.riscv64', 'RISC-V 64-bit UEFI clients receive the riscv64 grub2 boot file' ); +is( $by_name{'xcat-riscv64'}{test}, 'option[93].hex == 0x001b', 'RISC-V 64-bit UEFI class matches IANA client architecture 27 only' ); +is( $fallback_by_name{'xcat-riscv64'}{'boot-file-name'}, 'boot/grub2/grub2.riscv64', 'riscv64 clients get grub2 even without xNBA loaders' ); +unlike( join( ' ', map { $_->{test} } @$classes ), qr/0x001[9ade]/, 'no class claims the RISC-V 32-bit or 128-bit architecture ids' ); my $xnba_classes = xCAT::DHCP::BootPolicy->kea_xnba_node_classes( xnba_efi => 1, @@ -141,4 +145,76 @@ is_deeply( 'xNBA network policy requires a next server' ); +# UEFI HTTP boot: firmware that boots over HTTP sends architecture id 28 and only +# accepts an offer whose boot file is a URL and whose reply is tagged HTTPClient. +my $httpboot = xCAT::DHCP::BootPolicy->kea_httpboot_network_classes( + net => '10.0.0.0', + prefix => 24, + next_server => '10.0.0.1', + tftpdir => '/tftpboot', +); +is_deeply( + $httpboot, + [ + { + name => 'xcat-riscv64-http-10.0.0.0_24', + test => 'option[93].hex == 0x001c', + additional_only => 1, + 'boot-file-name' => 'http://10.0.0.1/tftpboot/boot/grub2/grub2.riscv64', + 'option-data' => [ + { + name => 'vendor-class-identifier', + data => 'HTTPClient', + 'always-send' => 1, + }, + ], + }, + ], + 'RISC-V HTTP boot clients are offered the boot loader as a URL, tagged HTTPClient' +); + +my $httpboot_port = xCAT::DHCP::BootPolicy->kea_httpboot_network_classes( + net => '10.0.0.0', + prefix => 24, + next_server => '10.0.0.1', + httpport => '8080', + tftpdir => '/srv/tftpboot', +); +is( + $httpboot_port->[0]{'boot-file-name'}, + 'http://10.0.0.1:8080/tftpboot/boot/grub2/grub2.riscv64', + 'the HTTP boot URL follows the configured HTTP port and web alias' +); + +is_deeply( + xCAT::DHCP::BootPolicy->kea_httpboot_network_classes( + net => '10.0.0.0', + prefix => 24, + next_server => '10.0.0.1', + loader_present => sub { 0 }, + ), + [], + 'no HTTP boot class is offered while the boot loader is missing' +); +is_deeply( + xCAT::DHCP::BootPolicy->kea_httpboot_network_classes( net => '10.0.0.0', prefix => 24 ), + [], + 'HTTP boot classes need a next server' +); +is( + scalar @{ xCAT::DHCP::BootPolicy->kea_httpboot_network_classes( + net => '10.0.0.0', + prefix => 24, + next_server => '10.0.0.1', + loader_present => sub { $_[0] eq '/tftpboot/boot/grub2/grub2.riscv64' }, + ) }, + 1, + 'the boot loader of the architecture is what is looked for' +); +unlike( + join( ' ', map { $_->{test} } @$classes ), + qr/0x001c/, + 'the global class list keeps HTTP boot out: it needs the address of the management node', +); + done_testing(); diff --git a/xCAT-test/unit/dhcp_isc_client_arch.t b/xCAT-test/unit/dhcp_isc_client_arch.t new file mode 100644 index 000000000..8cec79091 --- /dev/null +++ b/xCAT-test/unit/dhcp_isc_client_arch.t @@ -0,0 +1,59 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../../perl-xCAT"; +use Test::More; + +use xCAT::DHCP::BootPolicy; + +my $rendered = join '', @{ xCAT::DHCP::BootPolicy->isc_client_architecture_lines( + next_server => '192.0.2.10', + portsuffix => ':8080', + tftpdir => '/srv/tftp', + net => '192.0.2.0', + prefix => 24, + ) }; + +like( + $rendered, + qr/client-architecture = 00:0b \{ #aaarch64\n\s+filename "boot\/grub2\/grub2\.aarch64";/, + 'the ISC policy renders the aarch64 boot branch', +); +like( + $rendered, + qr/client-architecture = 00:1b \{ #riscv64 uefi\n\s+filename "boot\/grub2\/grub2\.riscv64";/, + 'the ISC policy renders the riscv64 TFTP boot branch', +); +like( + $rendered, + qr/client-architecture = 00:1c \{ #riscv64 uefi http boot\n\s+option vendor-class-identifier "HTTPClient";\n\s+filename "http:\/\/192\.0\.2\.10:8080\/tftpboot\/boot\/grub2\/grub2\.riscv64";/, + 'the ISC policy renders the riscv64 HTTP boot branch with the subnet URL', +); +like( + $rendered, + qr/option conf-file = "http:\/\/192\.0\.2\.10:8080\/tftpboot\/pxelinux\.cfg\/p\/192\.0\.2\.0_24";/, + 'the existing OPAL branch keeps its subnet URL', +); + +my @riscv_ids = $rendered =~ /client-architecture = (00:1[9a-e])/g; +is_deeply( + \@riscv_ids, + [ '00:1b', '00:1c' ], + 'only the RISC-V 64-bit UEFI architecture ids are mapped', +); + +my $aarch64_pos = index($rendered, 'client-architecture = 00:0b'); +my $tftp_pos = index($rendered, 'client-architecture = 00:1b'); +my $http_pos = index($rendered, 'client-architecture = 00:1c'); +my $opal_pos = index($rendered, 'client-architecture = 00:0e'); +my $fallback_pos = index($rendered, 'substring(filename,0,1) = null'); + +cmp_ok($aarch64_pos, '<', $tftp_pos, 'riscv64 follows the aarch64 branch'); +cmp_ok($tftp_pos, '<', $http_pos, 'the TFTP branch precedes the HTTP branch'); +cmp_ok($http_pos, '<', $opal_pos, 'the HTTP branch precedes the OPAL branch'); +cmp_ok($http_pos, '<', $fallback_pos, 'the HTTP branch is reachable before the fallback'); +like($rendered, qr/filename "\/yaboot";\n\s*\}\n\z/, 'the policy ends with the existing yaboot fallback'); + +done_testing(); diff --git a/xCAT-test/unit/dhcp_kea_option_flags.t b/xCAT-test/unit/dhcp_kea_option_flags.t new file mode 100644 index 000000000..9cb7b34d7 --- /dev/null +++ b/xCAT-test/unit/dhcp_kea_option_flags.t @@ -0,0 +1,139 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../../perl-xCAT"; + +use JSON qw(decode_json); +use Test::More; + +use xCAT::DHCP::Backend::Kea; + +my $backend = xCAT::DHCP::Backend::Kea->new( kea_version => '2.4.1' ); + +sub option_flags { + return { + name => 'domain-name', + data => 'example.test', + 'always-send' => 1, + 'csv-format' => 0, + 'never-send' => 0, + }; +} + +sub assert_boolean_flags { + my ( $option, $scope ) = @_; + + foreach my $flag (qw/always-send csv-format never-send/) { + is( + ref( $option->{$flag} ), + 'JSON::PP::Boolean', + "$scope renders $flag as a JSON boolean", + ); + } + ok( $option->{'always-send'}, "$scope preserves a true flag" ); + ok( !$option->{'csv-format'}, "$scope preserves a false flag" ); + ok( !$option->{'never-send'}, "$scope preserves a second false flag" ); +} + +my $dhcp4 = decode_json( + $backend->render_dhcp4_config( + { + 'option-data' => [ option_flags() ], + subnet4 => [ + { + id => 1, + subnet => '192.0.2.0/24', + 'option-data' => [ option_flags() ], + reservations => [ + { + 'hw-address' => '00:11:22:33:44:55', + 'ip-address' => '192.0.2.10', + 'option-data' => [ option_flags() ], + }, + ], + pools => [ + { + pool => '192.0.2.100 - 192.0.2.110', + 'option-data' => [ option_flags() ], + }, + ], + }, + ], + } + ) +)->{Dhcp4}; + +assert_boolean_flags( $dhcp4->{'option-data'}[0], 'DHCPv4 global option-data' ); +assert_boolean_flags( $dhcp4->{subnet4}[0]{'option-data'}[0], 'DHCPv4 subnet option-data' ); +assert_boolean_flags( $dhcp4->{subnet4}[0]{reservations}[0]{'option-data'}[0], + 'DHCPv4 reservation option-data' ); +assert_boolean_flags( $dhcp4->{subnet4}[0]{pools}[0]{'option-data'}[0], + 'DHCPv4 pool option-data' ); + +my $dhcp6 = decode_json( + $backend->render_dhcp6_config( + { + 'option-data' => [ option_flags() ], + subnet6 => [ + { + id => 2, + subnet => '2001:db8::/64', + 'option-data' => [ option_flags() ], + reservations => [ + { + duid => '00:01:00:01:de:ad:be:ef', + 'ip-addresses' => ['2001:db8::10'], + 'option-data' => [ option_flags() ], + }, + ], + pools => [ + { + pool => '2001:db8::100 - 2001:db8::110', + 'option-data' => [ option_flags() ], + }, + ], + }, + ], + } + ) +)->{Dhcp6}; + +assert_boolean_flags( $dhcp6->{'option-data'}[0], 'DHCPv6 global option-data' ); +assert_boolean_flags( $dhcp6->{subnet6}[0]{'option-data'}[0], 'DHCPv6 subnet option-data' ); +assert_boolean_flags( $dhcp6->{subnet6}[0]{reservations}[0]{'option-data'}[0], + 'DHCPv6 reservation option-data' ); +assert_boolean_flags( $dhcp6->{subnet6}[0]{pools}[0]{'option-data'}[0], + 'DHCPv6 pool option-data' ); + +my @control_agent_payloads; +my $live_backend = xCAT::DHCP::Backend::Kea->new( + kea_version => '2.4.1', + control_agent_handler => sub { + my ($payload) = @_; + push @control_agent_payloads, $payload; + return { ok => 1 }; + }, +); +is_deeply( + $live_backend->live_upsert_reservations( + [ + { + 'hw-address' => '00:11:22:33:44:55', + 'ip-address' => '192.0.2.10', + 'option-data' => [ option_flags() ], + }, + ], + ), + { ok => 1 }, + 'live reservation update succeeds', +); +is( $control_agent_payloads[0]{command}, 'reservation-add', + 'live reservation update reaches the Kea add command' ); +assert_boolean_flags( + $control_agent_payloads[0]{arguments}{reservation}{'option-data'}[0], + 'live reservation option-data', +); + +done_testing(); diff --git a/xCAT-test/unit/dhcp_kea_renderer.t b/xCAT-test/unit/dhcp_kea_renderer.t index af20211d5..e9b31c708 100644 --- a/xCAT-test/unit/dhcp_kea_renderer.t +++ b/xCAT-test/unit/dhcp_kea_renderer.t @@ -87,12 +87,39 @@ my $json = $backend->render_dhcp4_config( { name => 'conf-file', data => 'http://10.0.0.1/tftpboot/pxelinux.cfg/p/10.0.0.0_24' }, ], }, + { + name => 'xcat-riscv64-http-10.0.0.0_24', + test => 'option[93].hex == 0x001c', + additional_only => 1, + 'boot-file-name' => 'http://10.0.0.1/tftpboot/boot/grub2/grub2.riscv64', + 'option-data' => [ + { + name => 'vendor-class-identifier', + data => 'HTTPClient', + 'always-send' => 1, + }, + ], + }, ], } ); my $config = decode_json($json); ok( $config->{Dhcp4}, 'renderer creates a Dhcp4 document' ); + +# Kea reads the option flags as booleans, so a caller that sets them the plain +# Perl way still produces a configuration the server parses. +my ($httpboot_class) = + grep { $_->{name} eq 'xcat-riscv64-http-10.0.0.0_24' } @{ $config->{Dhcp4}{'client-classes'} || [] }; +ok( $httpboot_class, 'a client class carrying option data is rendered' ); +is( + ref( $httpboot_class->{'option-data'}[0]{'always-send'} ), + 'JSON::PP::Boolean', + 'the always-send option flag is rendered as a boolean, not as a number', +); +ok( $httpboot_class->{'option-data'}[0]{'always-send'}, 'and it is true' ); +is( $httpboot_class->{'option-data'}[0]{data}, 'HTTPClient', 'the option value is left alone' ); +is( $httpboot_class->{'boot-file-name'}, 'http://10.0.0.1/tftpboot/boot/grub2/grub2.riscv64', 'the boot file URL is left alone' ); is_deeply( $config->{Dhcp4}{'interfaces-config'}{interfaces}, ['eth0'], 'interfaces are rendered' ); is( $config->{Dhcp4}{'valid-lifetime'}, 600, 'valid lifetime is rendered' ); is( $config->{Dhcp4}{'lease-database'}{type}, 'memfile', 'memfile lease backend is the default' ); @@ -158,6 +185,19 @@ is_deeply( { name => 'conf-file', data => 'http://10.0.0.1/tftpboot/pxelinux.cfg/p/10.0.0.0_24' }, ], }, + { + name => 'xcat-riscv64-http-10.0.0.0_24', + test => 'option[93].hex == 0x001c', + 'only-if-required' => JSON::true, + 'boot-file-name' => 'http://10.0.0.1/tftpboot/boot/grub2/grub2.riscv64', + 'option-data' => [ + { + name => 'vendor-class-identifier', + data => 'HTTPClient', + 'always-send' => JSON::true, + }, + ], + }, ], 'client classes are preserved, including subnet-specific OPAL conf-file class' ); diff --git a/xCAT-test/unit/el_riscv64_install_paths.t b/xCAT-test/unit/el_riscv64_install_paths.t new file mode 100644 index 000000000..f7d7d7c45 --- /dev/null +++ b/xCAT-test/unit/el_riscv64_install_paths.t @@ -0,0 +1,128 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use File::Path qw(make_path); +use File::Slurper qw(write_text); +use File::Spec; +use File::Temp qw(tempdir); +use FindBin; +use Test::More; +BEGIN { + $ENV{XCATROOT} = "$FindBin::Bin/../../xCAT-server"; +} + +use lib "$FindBin::Bin/../../perl-xCAT"; +use lib "$FindBin::Bin/../../xCAT-server/lib/perl"; +use lib "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins"; +use lib "$FindBin::Bin/../../xCAT-server/share/xcat/netboot/imgutils"; + +require anaconda; +require geninitrd; +use imgutils; + +my $media = tempdir(CLEANUP => 1); +my $pxeboot = File::Spec->catdir($media, 'images', 'pxeboot'); +make_path($pxeboot); +write_text(File::Spec->catfile($pxeboot, 'vmlinuz'), "kernel\n"); +write_text(File::Spec->catfile($pxeboot, 'initrd.img'), "initrd\n"); + +my @paths = xCAT_plugin::anaconda::_install_media_pxeboot_paths($media, 'riscv64'); +is_deeply( + \@paths, + [ File::Spec->catfile($pxeboot, 'vmlinuz'), File::Spec->catfile($pxeboot, 'initrd.img') ], + 'riscv64 installer media use the images/pxeboot kernel and initrd', +); +is_deeply( + [ xCAT_plugin::anaconda::_install_media_pxeboot_paths($media, 'x86_64') ], + \@paths, + 'x86_64 keeps the existing images/pxeboot layout', +); +is_deeply( + [ xCAT_plugin::anaconda::_install_media_pxeboot_paths($media, 'aarch64') ], + \@paths, + 'aarch64 keeps the existing images/pxeboot layout', +); +is_deeply( + [ xCAT_plugin::anaconda::_install_media_pxeboot_paths($media, 'ppc64') ], + [], + 'POWER media continue through their separate layout', +); +unlink(File::Spec->catfile($pxeboot, 'initrd.img')); +is_deeply( + [ xCAT_plugin::anaconda::_install_media_pxeboot_paths($media, 'riscv64') ], + [ File::Spec->catfile($pxeboot, 'vmlinuz'), undef ], + 'an incomplete pxeboot media pair preserves the existing partial lookup result', +); + +is( + xCAT_plugin::anaconda::_driver_disk_kernel_version('/tmp/rpm/boot/vmlinuz-6.12.0-55.riscv64'), + '6.12.0-55.riscv64', + 'driver-disk kernel updates recognise riscv64 kernels', +); +is( + xCAT_plugin::anaconda::_driver_disk_kernel_version('/tmp/rpm/boot/vmlinuz-6.12.0-55.x86_64'), + '6.12.0-55.x86_64', + 'driver-disk kernel updates still recognise x86_64 kernels', +); +is( + xCAT_plugin::anaconda::_driver_disk_kernel_version('/tmp/rpm/boot/not-a-kernel-riscv64'), + undef, + 'driver-disk kernel parsing rejects unrelated files', +); + +ok( + xCAT_plugin::geninitrd::_uses_x86_install_media_layout('riscv64', 'rocky10'), + 'EL riscv64 media use the installer pxeboot layout', +); +ok( + !xCAT_plugin::geninitrd::_uses_x86_install_media_layout('riscv64', 'sles15'), + 'SLES riscv64 media do not enter the EL layout', +); +ok( + xCAT_plugin::geninitrd::_uses_x86_install_media_layout('x86_64', 'sles15'), + 'x86_64 SLES media retain their existing outer architecture route', +); +ok( + !xCAT_plugin::geninitrd::_uses_x86_install_media_layout('ppc64le', 'rocky10'), + 'POWER media retain their separate architecture route', +); + +is_deeply( + [ imgutils::default_net_drivers( 'rh', 'riscv64' ) ], + [qw(e1000 e1000e igb ixgbe r8169 tg3 bnx2x mlx5_core virtio_net)], + 'riscv64 diskless images receive the intended default network drivers', +); +is_deeply( + [ imgutils::resolver_library_paths('riscv64') ], + [ 'lib64/libnss_dns.so.2', 'lib64/libresolv.so.2' ], + 'riscv64 images take resolver libraries from lib64', +); +is_deeply( + [ imgutils::resolver_library_paths('ppc64') ], + [ 'lib/libnss_dns.so.2', 'lib/libresolv.so.2' ], + 'POWER images retain the lib resolver paths', +); + +is( + xCAT_plugin::anaconda::_default_crashkernel_args('riscv64', '/dev/vda2', 0, '', ''), + ' crashkernel=256M dump=/dev/vda2 ', + 'riscv64 images reserve the existing default crash kernel size', +); +is( + xCAT_plugin::anaconda::_default_crashkernel_args('x86_64', '/dev/sda2', 0, '', ''), + ' crashkernel=128M dump=/dev/sda2 ', + 'x86_64 keeps its existing default crash kernel arguments', +); +is( + xCAT_plugin::anaconda::_default_crashkernel_args('ppc64', '/dev/sda2', 0, '', ''), + ' crashkernel=256M@64M dump=/dev/sda2 ', + 'POWER keeps its existing default crash kernel arguments', +); +is( + xCAT_plugin::anaconda::_default_crashkernel_args('ppc64', 'nfs', 1, 'net,host:/dump', 'nfs://host/dump'), + ' fadump=on fadump_reserve_mem=512M fadump_target=net,host:/dump fadump_default=noreboot dump=nfs://host/dump ', + 'POWER fadump keeps its existing default arguments', +); + +done_testing(); diff --git a/xCAT-test/unit/genesis_openembedded_activation.t b/xCAT-test/unit/genesis_openembedded_activation.t index 8294c57df..7aeef2c5c 100644 --- a/xCAT-test/unit/genesis_openembedded_activation.t +++ b/xCAT-test/unit/genesis_openembedded_activation.t @@ -13,6 +13,7 @@ my $rpm_weak_dependencies = join( '%if 0%{?fedora} || 0%{?rhel} >= 8 || 0%{?suse_version} >= 1500', 'Recommends: xCAT-genesis-openembedded-x86_64', 'Recommends: xCAT-genesis-openembedded-ppc64le', + 'Recommends: xCAT-genesis-openembedded-riscv64', '%endif', ); @@ -39,6 +40,11 @@ like( qr/^Recommends:.*\bxcat-genesis-openembedded-ppc64le\b/m, 'DEB installations recommend the first-class ppc64le image', ); +like( + $deb_control, + qr/^Recommends:.*\bxcat-genesis-openembedded-riscv64\b/m, + 'DEB installations recommend the first-class riscv64 image', +); unlike( $deb_control, qr/^Depends:.*\bxcat-genesis-openembedded-/m, @@ -63,6 +69,11 @@ like( qr/^Recommends:.*\bxcat-genesis-openembedded-ppc64le\b/m, 'DEB service nodes recommend the first-class ppc64le image', ); +like( + $sn_deb_control, + qr/^Recommends:.*\bxcat-genesis-openembedded-riscv64\b/m, + 'DEB service nodes recommend the first-class riscv64 image', +); my $go_xcat = slurp_repo_file('xCAT-server/share/xcat/tools/go-xcat'); unlike( diff --git a/xCAT-test/unit/install_profile_riscv64.t b/xCAT-test/unit/install_profile_riscv64.t new file mode 100644 index 000000000..117110baf --- /dev/null +++ b/xCAT-test/unit/install_profile_riscv64.t @@ -0,0 +1,178 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use File::Path qw(make_path); +use File::Slurper qw(read_text write_text); +use File::Spec; +use File::Temp qw(tempdir); +use FindBin; +use lib "$FindBin::Bin/../lib"; +use Test::More; + +use XCAT::Test::File qw(repo_path slurp_repo_file); + +# EL10 riscv64 kickstart installs: the EL10 anaconda has no RISC-V EFI +# platform, so the riscv64 templates tolerate its x86 boot loader package +# request, pull grub2-efi-riscv64/efibootmgr in through the riscv64 package +# lists, and run a %post that points the UEFI boot entry at grubriscv64.efi +# and places the removable-media fallback loader. + +my $install = File::Spec->catdir( 'xCAT-server', 'share', 'xcat', 'install' ); + +for my $family ( [ 'rocky', 'rocky10' ], [ 'rh', 'rhels10' ] ) { + my ( $dir, $osbase ) = @$family; + for my $profile (qw(compute service)) { + my $tmpl = File::Spec->catfile( $install, $dir, "$profile.$osbase.riscv64.tmpl" ); + my $pkglist = File::Spec->catfile( $install, $dir, "$profile.$osbase.riscv64.pkglist" ); + my $base = File::Spec->catfile( $install, $dir, "$profile.$osbase.tmpl" ); + ok( -r repo_path($tmpl), "$dir/$profile.$osbase.riscv64.tmpl exists" ); + ok( -r repo_path($pkglist), "$dir/$profile.$osbase.riscv64.pkglist exists" ); + my $t = slurp_repo_file($tmpl); + like( $t, qr/^%packages --ignoremissing$/m, "$dir/$profile.$osbase riscv64 template tolerates anaconda's x86 boot loader package request" ); + like( $t, qr/^#INCLUDE_DEFAULT_PKGLIST#$/m, "$dir/$profile.$osbase riscv64 template still includes the default package list" ); + like( $t, qr{^#INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/post\.rhels10\.riscv64#$}m, "$dir/$profile.$osbase riscv64 template runs the riscv64 UEFI boot entry fix-up" ); + like( $t, qr/^%addon com_redhat_kdump --disable\n%end$/m, "$dir/$profile.$osbase riscv64 template keeps the installer from writing a crash kernel reservation EL10 riscv64 cannot honour" ); + like( $t, qr/^%post --erroronfail --interpreter=\/bin\/bash$/m, "$dir/$profile.$osbase riscv64 template fails the install when no boot loader reached the ESP" ); + like( $t, qr{/boot/efi/EFI/BOOT/BOOTRISCV64\.EFI}, "$dir/$profile.$osbase riscv64 template checks the removable-media loader" ); + like( $t, qr{/boot/efi/EFI/\*/grubriscv64\.efi}, "$dir/$profile.$osbase riscv64 template also accepts a vendor directory loader" ); + like( $t, qr/^rootpw --iscrypted #CRYPT:passwd:key=system,username=root:password#$/m, "$dir/$profile.$osbase riscv64 template keeps the shared root password directive" ); + + # the riscv64 template is the shared one plus the riscv64 changes + my $b = slurp_repo_file($base); + ( my $t_norm = $t ) =~ s/^# riscv64:.*\n(?:#.*\n)*%addon com_redhat_kdump --disable\n%end\n\n//m; + $t_norm =~ s/^# riscv64:.*\n(?:#.*\n)*//m; + $t_norm =~ s/^%packages --ignoremissing$/%packages/m; + $t_norm =~ s{^#INCLUDE:#ENV:XCATROOT#/share/xcat/install/scripts/post\.rhels10\.riscv64#\n}{}m; + $t_norm =~ s/\n# --ignoremissing above.*?\n%post --erroronfail.*?\n%end\n//s; + is( $t_norm, $b, "$dir/$profile.$osbase riscv64 template only differs from the shared template in the riscv64 changes" ); + + my $p = slurp_repo_file($pkglist); + like( $p, qr/^grub2-efi-riscv64$/m, "$dir/$profile.$osbase riscv64 package list installs grub2-efi-riscv64" ); + like( $p, qr/^efibootmgr$/m, "$dir/$profile.$osbase riscv64 package list installs efibootmgr" ); + unlike( $p, qr/shim|grub2-efi-x64|grub2-efi-aa64/, "$dir/$profile.$osbase riscv64 package list asks for no other architecture's boot loader" ); + my $shared = slurp_repo_file( + File::Spec->catfile( $install, $dir, "$profile.$osbase.pkglist" ) + ); + ( my $p_norm = $p ) =~ s/^grub2-efi-riscv64\n//m; + $p_norm =~ s/^efibootmgr\n//m; + s/\s+\z/\n/ for ( $p_norm, $shared ); + is( $p_norm, $shared, "$dir/$profile.$osbase riscv64 package list is the shared list plus the boot loader packages" ); + } +} + +my $post = File::Spec->catfile( $install, 'scripts', 'post.rhels10.riscv64' ); +my $post_path = repo_path($post); +ok( -r $post_path, 'post.rhels10.riscv64 exists' ); + +is(system('sh', '-n', $post_path), 0, 'post.rhels10.riscv64 parses as POSIX shell'); + +my $tools = tempdir(CLEANUP => 1); +my $efi_log = File::Spec->catfile($tools, 'efibootmgr.log'); + +sub fake_command { + my ($name, $body) = @_; + my $path = File::Spec->catfile($tools, $name); + write_text($path, "#!/bin/sh\n$body"); + chmod 0755, $path or die "Unable to make $path executable: $!"; + return $path; +} + +my $uname = fake_command('uname', <<'SH'); +printf '%s\n' "${XCAT_TEST_ARCH:-riscv64}" +SH +my $findmnt = fake_command('findmnt', <<'SH'); +printf '/dev/vda1\n' +SH +my $lsblk = fake_command('lsblk', <<'SH'); +case "$*" in + *PKNAME*) printf "vda\n" ;; + *PARTN*) printf "1\n" ;; +esac +SH +my $efibootmgr = fake_command('efibootmgr', <<'SH'); +printf '%s\n' "$*" >> "$XCAT_EFI_LOG" +case "$*" in + '') exit 0 ;; + '-v') + printf 'Boot0001* Rocky HD(1,GPT,...)/File(\\EFI\\rocky\\shimx64.efi)\n' + printf 'Boot0002* OldRiscv HD(1,GPT,...)/File(\\EFI\\rocky\\grubriscv64.efi)\n' + printf 'Boot0003* Network PXE\n' + exit 0 + ;; + *'-c'*) exit "${XCAT_EFI_CREATE_RC:-0}" ;; +esac +exit 0 +SH + +sub installed_root { + my ($with_loader) = @_; + my $root = tempdir(CLEANUP => 1); + make_path(File::Spec->catdir($root, 'boot', 'efi', 'EFI', 'rocky')); + make_path(File::Spec->catdir($root, 'etc')); + write_text(File::Spec->catfile($root, 'etc', 'os-release'), "NAME=\"Rocky Linux\"\n"); + if ($with_loader) { + write_text( + File::Spec->catfile($root, 'boot', 'efi', 'EFI', 'rocky', 'grubriscv64.efi'), + "riscv loader\n", + ); + } + return $root; +} + +sub run_post { + my ($root, %extra) = @_; + local %ENV = ( + %ENV, + XCAT_INSTALL_ROOT => $root, + XCAT_UNAME => $uname, + XCAT_EFIBOOTMGR => $efibootmgr, + XCAT_FINDMNT => $findmnt, + XCAT_LSBLK => $lsblk, + XCAT_EFI_LOG => $efi_log, + XCAT_TEST_ARCH => 'riscv64', + %extra, + ); + open(my $fh, '-|', 'sh', $post_path) or die "Unable to run $post_path: $!"; + my $output = do { local $/; <$fh> }; + close($fh); + return ($? >> 8, $output); +} + +my $root = installed_root(1); +my ($status, $output) = run_post($root); +is($status, 0, 'the RISC-V fix-up completes successfully'); +is( + read_text(File::Spec->catfile($root, 'boot', 'efi', 'EFI', 'BOOT', 'BOOTRISCV64.EFI')), + "riscv loader\n", + 'the distro loader is copied to the removable-media fallback path', +); +like($output, qr/UEFI boot entry "Rocky Linux"/, 'the created UEFI entry is reported'); +my $efi_calls = read_text($efi_log); +like($efi_calls, qr/^-q -b 0001 -B$/m, 'the invalid x86 boot entry is removed'); +like($efi_calls, qr/^-q -b 0002 -B$/m, 'the prior RISC-V boot entry is removed'); +unlike($efi_calls, qr/0003/, 'unrelated firmware entries are retained'); +like( + $efi_calls, + qr/^-q -c -d \/dev\/vda -p 1 -L Rocky Linux -l \\EFI\\rocky\\grubriscv64\.efi$/m, + 'the new entry points at the distro RISC-V loader on the ESP disk', +); + +unlink($efi_log); +my $x86_root = installed_root(1); +($status, $output) = run_post($x86_root, XCAT_TEST_ARCH => 'x86_64'); +is($status, 0, 'the post-install script is a no-op on x86_64'); +is($output, '', 'the x86_64 no-op reports nothing'); +ok(!-e $efi_log, 'the x86_64 no-op never invokes efibootmgr'); + +my $bare_root = installed_root(0); +($status, $output) = run_post($bare_root); +is($status, 0, 'a missing distro loader remains nonfatal'); +like($output, qr/no \\EFI\\\*\\grubriscv64\.efi/, 'a missing distro loader is diagnosed'); + +unlink($efi_log); +($status, $output) = run_post($root, XCAT_EFI_CREATE_RC => 1); +is($status, 0, 'an efibootmgr create failure remains nonfatal'); +like($output, qr/firmware will use \\EFI\\BOOT\\BOOTRISCV64\.EFI/, 'the fallback path is reported after an efibootmgr failure'); + +done_testing(); diff --git a/xCAT-test/unit/lookup_netboot_arch.t b/xCAT-test/unit/lookup_netboot_arch.t new file mode 100644 index 000000000..00d22cc15 --- /dev/null +++ b/xCAT-test/unit/lookup_netboot_arch.t @@ -0,0 +1,71 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../lib"; +use lib "$FindBin::Bin/../../perl-xCAT"; +use Test::More; + +use XCAT::Test::File qw(repo_path); +use xCAT::ProfiledNodeUtils; +use xCAT::Utils; + +# riscv64 nodes boot through UEFI and grub2 only. This test pins the three +# places that declare which noderes.netboot values an architecture accepts: +# xCAT::Utils::lookupNetboot (used by nodeset/rinstall validation), the +# profiled-node netboot rule table in xCAT::ProfiledNodeUtils, and the +# schema descriptions that document the valid values. +# +# --------------------------------------------------------------------------- +# xCAT::Utils::lookupNetboot +# --------------------------------------------------------------------------- +my @lookup_cases = ( + [ 'rocky10.2', 'riscv64', 'Linux', 'grub2,grub2-tftp,grub2-http', 'riscv64 EL10 boots with grub2 over tftp or http' ], + [ 'rhels10.2', 'riscv64', 'Linux', 'grub2,grub2-tftp,grub2-http', 'riscv64 RHEL 10 uses the same grub2 methods' ], + [ 'ubuntu24.04.4', 'riscv64', 'Linux', 'grub2,grub2-tftp,grub2-http', 'riscv64 is arch-driven, not distro-driven' ], + [ 'rhels10.2', 'RISCV64', 'Linux', 'grub2,grub2-tftp,grub2-http', 'the arch match is case-insensitive like the other arches' ], + [ 'rhels9.4', 'aarch64', 'Linux', 'grub2', 'aarch64 keeps its single grub2 method' ], + [ 'rhels9.4', 'x86_64', 'Linux', 'xnba,pxe,grub2', 'x86_64 methods are unchanged' ], + [ 'rhels9.4', 'ppc64le', 'Linux', 'petitboot,grub2,grub2-tftp,grub2-http', 'ppc64le methods are unchanged' ], + [ 'rhels10.2', 'riscv64', 'NIM', 'nimol', 'NIM images are not affected by the arch' ], + [ 'rhels10.2', 'riscv32', 'Linux', '', 'unknown architectures still resolve to no netboot method' ], +); + +for my $case (@lookup_cases) { + my ( $osvers, $osarch, $imgtype, $expected, $label ) = @$case; + is( xCAT::Utils->lookupNetboot( $osvers, $osarch, $imgtype ), $expected, $label ); +} + +# --------------------------------------------------------------------------- +# xCAT::ProfiledNodeUtils netboot rule table + cal_netboot +# --------------------------------------------------------------------------- +my $rule_table = \%xCAT::ProfiledNodeUtils::NETBOOT_RULES; + +is( xCAT::ProfiledNodeUtils::cal_netboot( $rule_table, [ 'riscv64', 'rhels', '10', '*' ] ), + 'grub2', 'profiled riscv64 nodes default to grub2' ); +is( xCAT::ProfiledNodeUtils::cal_netboot( $rule_table, [ 'riscv64', 'rhels', '10', 'ipmi' ] ), + 'grub2', 'riscv64 grub2 does not depend on the management method' ); +is( xCAT::ProfiledNodeUtils::cal_netboot( $rule_table, [ 'x86_64', 'rhels', '10', '*' ] ), + 'xnba', 'x86_64 profiled nodes still default to xnba' ); +is( xCAT::ProfiledNodeUtils::cal_netboot( $rule_table, [ 'ppc64le', 'rhels', '9', 'ipmi' ] ), + 'petitboot', 'ppc64le ipmi profiled nodes still default to petitboot' ); +is( xCAT::ProfiledNodeUtils::cal_netboot( $rule_table, [ 'aarch64', 'rhels', '9', '*' ] ), + '0', 'aarch64 profiled nodes are still undefined in the rule table' ); + +# --------------------------------------------------------------------------- +# Schema descriptions +# --------------------------------------------------------------------------- +SKIP: { + skip 'xCAT::Schema is not loadable here', 3 + unless eval { require lib; lib->import( repo_path('perl-xCAT') ); require xCAT::Schema; 1 }; + + like( $xCAT::Schema::tabspec{nodetype}{descriptions}{arch}, qr/\briscv64\b/, + 'nodetype.arch documents riscv64 as a valid value' ); + like( $xCAT::Schema::tabspec{osimage}{descriptions}{osarch}, qr/\briscv64\b/, + 'osimage.osarch documents riscv64 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' ); +} + +done_testing(); diff --git a/xCAT-test/unit/mknb_xcatd_address.t b/xCAT-test/unit/mknb_xcatd_address.t index 56339f98e..aa2c77d4b 100644 --- a/xCAT-test/unit/mknb_xcatd_address.t +++ b/xCAT-test/unit/mknb_xcatd_address.t @@ -4,19 +4,23 @@ use warnings; ## no critic (Modules::RequireFilenameMatchesPackage, TestingAndDebugging::ProhibitNoStrict, TestingAndDebugging::ProhibitNoWarnings) use FindBin; +use lib "$FindBin::Bin/../lib"; +use File::Slurper qw(write_text); use File::Path qw(make_path); use File::Temp qw(tempdir); use Test::More; +use XCAT::Test::File qw(repo_path); BEGIN { package xCAT::Utils; $INC{'xCAT/Utils.pm'} = __FILE__; package xCAT::TableUtils; - our ($tftpdir, $site_master, $site_httpport); + our ($tftpdir, $site_master, $site_httpport, %site_extra); sub getTftpDir { return $tftpdir; } sub get_site_attribute { my $attribute = $_[-1]; + return ($site_extra{$attribute}) if exists $site_extra{$attribute}; return ($site_master) if $attribute eq 'master'; return ($site_httpport) if $attribute eq 'httpport' and defined $site_httpport; return; @@ -36,6 +40,8 @@ BEGIN { return $hexnet_addresses; } sub getipaddr { return @master_addresses; } + our $nic_ips; + sub get_nic_ip { return $nic_ips || {}; } $INC{'xCAT/NetworkUtils.pm'} = __FILE__; package xCAT::NodeRange; @@ -47,12 +53,8 @@ BEGIN { $INC{'xCAT/NodeRange.pm'} = __FILE__; } -my $source_mknb_plugin = "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/mknb.pm"; -if (-f $source_mknb_plugin) { - require $source_mknb_plugin; -} else { - require xCAT_plugin::mknb; -} +my $source_mknb_plugin = repo_path('xCAT-server/lib/xcat/plugins/mknb.pm'); +require $source_mknb_plugin; my ($legacy, $selected) = xCAT_plugin::mknb::_select_network_addresses( { @@ -334,4 +336,176 @@ is( 'legacy PXE output is byte-identical without a site.master preference', ); +# riscv64 discovery boots through UEFI firmware and grub2: mknb writes one +# grub2 configuration per network, named by the network's hex prefix the same +# way PXELINUX files are, instead of PXELINUX/xNBA or petitboot files. +make_path("$::XCATROOT/share/xcat/netboot/genesis/riscv64"); +use_reporter_address_maps(); +prepare_tftpdir($tmpdir, 'tftpboot-riscv64', 'riscv64'); +$responses = run_mknb('riscv64'); +generation_succeeded($responses, 'riscv64 configuration generation succeeds'); + +my $grub_cfg_path = "$xCAT::TableUtils::tftpdir/boot/grub2/grub.cfg-C0A89"; +ok(-f $grub_cfg_path, 'riscv64 writes a grub2 configuration named by the network hex prefix'); +my $grub_cfg = read_config($grub_cfg_path); +like( + $grub_cfg, + qr/^# xCAT Genesis discovery for network C0A89 - generated by mknb, do not edit$/m, + 'the grub2 discovery configuration names its network and generator', +); +like($grub_cfg, qr/^set default=0$/m, 'the grub2 discovery configuration selects the first entry'); +like($grub_cfg, qr/^set timeout=5$/m, 'the grub2 discovery configuration boots after a short timeout'); +like($grub_cfg, qr/^set fallback=1$/m, 'a payload that cannot be fetched over HTTP falls back to the TFTP entry'); +like( + $grub_cfg, + qr/^if \[ "\$grub_cpu" = "riscv64" \]; then$/m, + 'the riscv64 menu entry is guarded by the GRUB cpu', +); +like($grub_cfg, qr/^menuentry "xCAT Genesis riscv64" \{$/m, 'the menu entry names the architecture'); +like( + $grub_cfg, + qr{^ linux /tftpboot/xcat/genesis\.kernel\.riscv64 xcatd=192\.168\.148\.10:3001 BOOTIF=\$net_default_mac$}m, + 'the kernel line loads the Genesis kernel with the xcatd endpoint and the booting MAC', +); +like( + $grub_cfg, + qr{^ initrd /tftpboot/xcat/genesis\.fs\.riscv64\.gz$}m, + 'the initrd line loads the Genesis initramfs', +); + +# the Genesis payload is large and TFTP serves one client at a time, so the default +# entry fetches it over HTTP and the TFTP entry stays behind it +like($grub_cfg, qr/^ set root=http,192\.168\.148\.10$/m, 'the default entry fetches the payload over HTTP'); +like($grub_cfg, qr/^ insmod http$/m, 'the HTTP entry loads the grub2 http module'); +my ($first_entry) = $grub_cfg =~ /^(menuentry .*?^\})/ms; +like($first_entry || '', qr/set root=http,/, 'the first, default entry is the HTTP one'); +like($grub_cfg, qr/^menuentry "xCAT Genesis riscv64 \(TFTP\)" \{$/m, 'a TFTP entry follows it'); +like( + $grub_cfg, + qr/^menuentry "xCAT Genesis riscv64 \(TFTP\)" \{\n insmod tftp\n set root=tftp,192\.168\.148\.10\n linux \/xcat\/genesis\.kernel\.riscv64 xcatd=192\.168\.148\.10:3001 BOOTIF=\$net_default_mac\n initrd \/xcat\/genesis\.fs\.riscv64\.gz\n\}$/m, + 'the TFTP entry keeps the paths relative to the TFTP root', +); +like($grub_cfg, qr/^\}\nfi\n\z/m, 'the configuration closes the menu entries and the cpu guard'); +unlike( + $grub_cfg, + qr/xcatd=192\.168\.149\.100:3001/, + 'the grub2 configuration does not use the later floating address as the xcatd endpoint', +); +ok(!-e "$xCAT::TableUtils::tftpdir/pxelinux.cfg/C0A89", 'riscv64 writes no PXELINUX configuration'); +ok(!-e "$xCAT::TableUtils::tftpdir/xcat/xnba", 'riscv64 writes no xNBA configuration'); +ok(!-e "$xCAT::TableUtils::tftpdir/etc/c0a89", 'riscv64 writes no petitboot configuration'); +ok(!-e "$xCAT::TableUtils::tftpdir/pxelinux.cfg/p", 'riscv64 writes no POWER network configuration'); +is_deeply( + [ xCAT_plugin::mknb::_grub2_discovery_arches($xCAT::TableUtils::tftpdir) ], + [ [ 'riscv64', 'riscv64', 'xcat/genesis.kernel.riscv64', 'xcat/genesis.fs.riscv64.gz' ] ], + 'the published riscv64 Genesis artifacts are enumerated relative to the TFTP root', +); + +# serial console settings reach the Genesis kernel line +%xCAT::TableUtils::site_extra = ( + defserialport => '0', + defserialspeed => '115200', + defserialflow => 'hard', + xcatdport => '3002', +); +$responses = run_mknb('riscv64'); +generation_succeeded($responses, 'riscv64 configuration generation succeeds with a serial console'); +like( + read_config($grub_cfg_path), + qr{^ linux /tftpboot/xcat/genesis\.kernel\.riscv64 xcatd=192\.168\.148\.10:3002 console=tty0 console=ttyS0,115200n8r BOOTIF=\$net_default_mac$}m, + 'the serial console and a non-default xcatd port are carried into the kernel line', +); +%xCAT::TableUtils::site_extra = (); + +# a non-default site.httpport reaches the HTTP entry +%xCAT::TableUtils::site_extra = ( httpport => '8080' ); +$responses = run_mknb('riscv64'); +generation_succeeded($responses, 'riscv64 configuration generation succeeds with a non-default HTTP port'); +like( + read_config($grub_cfg_path), + qr/^ set root=http,192\.168\.148\.10:8080$/m, + 'the HTTP entry uses the configured HTTP port', +); +like( + read_config($grub_cfg_path), + qr/^ set root=tftp,192\.168\.148\.10$/m, + 'the TFTP entry is unaffected by the HTTP port', +); +%xCAT::TableUtils::site_extra = (); + +# an lzma initramfs is preferred when mknb produced one +write_text( + "$xCAT::TableUtils::tftpdir/xcat/genesis.fs.riscv64.lzma", '' +); +$responses = run_mknb('riscv64'); +generation_succeeded($responses, 'riscv64 configuration generation succeeds with an lzma initramfs'); +like( + read_config($grub_cfg_path), + qr{^ initrd /tftpboot/xcat/genesis\.fs\.riscv64\.lzma$}m, + 'the lzma Genesis initramfs is preferred over the gzip one', +); +unlink("$xCAT::TableUtils::tftpdir/xcat/genesis.fs.riscv64.lzma"); + +# the discovery configurations are only reachable through grub2., which mknb does not +# build: a missing boot loader is reported instead of failing silently in firmware +{ + my $loader = "$xCAT::TableUtils::tftpdir/boot/grub2/grub2.riscv64"; + my $missing = run_mknb('riscv64'); + generation_succeeded($missing, 'riscv64 configuration generation succeeds without the grub2 boot loader'); + ok( + scalar(grep { ref($_) eq 'HASH' && $_->{data} && "@{$_->{data}}" =~ m{\Qboot/grub2/grub2.riscv64\E is missing} } @{$missing}), + 'a missing grub2.riscv64 boot loader is reported', + ); + make_path("$xCAT::TableUtils::tftpdir/boot/grub2"); + write_text( $loader, '' ); + my $present = run_mknb('riscv64'); + ok( + !grep({ ref($_) eq 'HASH' && $_->{data} && "@{$_->{data}}" =~ /is missing/ } @{$present}), + 'nothing is reported once the boot loader is in place', + ); + unlink($loader); +} + + +# the configuration is rebuilt from what is published: without artifacts it goes away +unlink("$xCAT::TableUtils::tftpdir/xcat/genesis.kernel.riscv64"); +is( + xCAT_plugin::mknb::_write_grub2_discovery_config( + tftpdir => $xCAT::TableUtils::tftpdir, + hexnet => 'c0a89', + xcatd_address => '192.168.148.10', + xcatdport => 3001, + ), + undef, + 'no grub2 discovery configuration is written without a published Genesis kernel', +); +ok(!-e $grub_cfg_path, 'a stale grub2 discovery configuration is removed with its artifacts'); + +# --configfileonly without published artifacts fails like the other architectures +$xCAT::TableUtils::tftpdir = "$tmpdir/tftpboot-riscv64-empty"; +make_path("$xCAT::TableUtils::tftpdir/xcat"); +$responses = run_mknb('riscv64'); +ok( + scalar(grep { ref($_) eq 'HASH' && $_->{error} && "@{$_->{error}}" =~ /No kernel file found/ } @{$responses}), + 'riscv64 --configfileonly without a kernel reports the missing kernel', +); + +# a :noboot interface keeps no discovery configuration for its network +%xCAT::TableUtils::site_extra = ( dhcpinterfaces => 'eth0,eth1:noboot' ); +$xCAT::NetworkUtils::nic_ips = { eth0 => '10.0.0.1', eth1 => '192.168.148.10' }; +use_reporter_address_maps(); +prepare_tftpdir($tmpdir, 'tftpboot-riscv64-noboot', 'riscv64'); +make_path("$xCAT::TableUtils::tftpdir/boot/grub2"); +write_text( + "$xCAT::TableUtils::tftpdir/boot/grub2/grub.cfg-C0A89", '' +); +$responses = run_mknb('riscv64'); +generation_succeeded($responses, 'riscv64 configuration generation succeeds with a :noboot interface'); +ok( + !-e "$xCAT::TableUtils::tftpdir/boot/grub2/grub.cfg-C0A89", + 'a network served by a :noboot interface gets no grub2 discovery configuration', +); +%xCAT::TableUtils::site_extra = (); +$xCAT::NetworkUtils::nic_ips = undef; + done_testing(); diff --git a/xCAT-test/unit/netboot_profile_riscv64.t b/xCAT-test/unit/netboot_profile_riscv64.t new file mode 100644 index 000000000..40c00dab9 --- /dev/null +++ b/xCAT-test/unit/netboot_profile_riscv64.t @@ -0,0 +1,103 @@ +#!/usr/bin/env perl +use strict; +use warnings; +## no critic (Modules::RequireFilenameMatchesPackage) + +use FindBin; +use lib "$FindBin::Bin/../lib"; +use lib "$FindBin::Bin/../../perl-xCAT"; +use lib "$FindBin::Bin/../../xCAT-server/lib/perl"; +use File::Spec; +use Test::More; + +use XCAT::Test::File qw(repo_path slurp_repo_file); +use xCAT::SvrUtils; + +# EL10 riscv64 diskless and service profiles are plain data files resolved by +# imgutils::get_profile_def_filename (osver.arch first, then osbase.arch, then +# the arch-less fallbacks). Pin that the riscv64 files exist, win the lookup +# for rocky10/rhels10 point releases, and carry the right content. + +my $share_relative = File::Spec->catdir( 'xCAT-server', 'share', 'xcat' ); +my $share = repo_path($share_relative); +my $imgutils_relative = File::Spec->catfile( + $share_relative, 'netboot', 'imgutils', 'imgutils.pm' +); +my $imgutils = repo_path($imgutils_relative); +plan skip_all => "$imgutils not found" unless -r $imgutils; +require $imgutils; + +my @families = ( + [ 'rocky', 'rocky10', 'rocky10.2' ], + [ 'rh', 'rhels10', 'rhels10.2' ], +); + +for my $family (@families) { + my ( $dir, $osbase, $osver ) = @$family; + my $base_relative = File::Spec->catdir( $share_relative, 'netboot', $dir ); + my $base = File::Spec->catdir( $share, 'netboot', $dir ); + + for my $profile (qw(compute service)) { + for my $ext (qw(pkglist exlist postinstall)) { + my $expected = File::Spec->catfile( $base, "$profile.$osbase.riscv64.$ext" ); + ok( -r $expected, "$dir/$profile.$osbase.riscv64.$ext exists" ); + is( + imgutils::get_profile_def_filename( $osver, $profile, 'riscv64', $base, $ext ), + $expected, + "$osver riscv64 $profile $ext resolves to the riscv64 file", + ); + } + my $x86 = slurp_repo_file( + File::Spec->catfile( $base_relative, "$profile.$osbase.x86_64.pkglist" ) + ); + my $rv = slurp_repo_file( + File::Spec->catfile( $base_relative, "$profile.$osbase.riscv64.pkglist" ) + ); + s/\s+\z/\n/ for ( $x86, $rv ); + is( $rv, $x86, "$dir/$profile.$osbase riscv64 pkglist matches the x86_64 list (no arch-specific packages)" ); + unlike( $rv, qr/^(?:microcode_ctl|grub2-efi-x64|shim-x64|syslinux|xnba)/m, "$dir/$profile.$osbase riscv64 pkglist has no x86-only packages" ); + + my $exlist = slurp_repo_file( + File::Spec->catfile( $base_relative, "$profile.$osbase.riscv64.exlist" ) + ); + like( $exlist, qr{^\./lib/kbd/keymaps/include\*$}m, "$dir/$profile.$osbase riscv64 exlist excludes the kbd keymap includes" ); + unlike( $exlist, qr{^\./lib/kdb/}m, "$dir/$profile.$osbase riscv64 exlist has no kdb typo" ); + is( scalar( () = $exlist =~ m{^\./usr/share/man\*$}mg ), 1, "$dir/$profile.$osbase riscv64 exlist lists usr/share/man once" ); + + my $postinstall = slurp_repo_file( + File::Spec->catfile( $base_relative, "$profile.$osbase.riscv64.postinstall" ) + ); + like( $postinstall, qr/^#!\/bin\/sh/, "$dir/$profile.$osbase riscv64 postinstall is a shell script" ); + like( $postinstall, qr/SELINUX=disabled/, "$dir/$profile.$osbase riscv64 postinstall disables SELinux in the image" ); + } + + my $otherpkgs = File::Spec->catfile( $base, "service.$osbase.riscv64.otherpkgs.pkglist" ); + my $otherpkgs_relative = File::Spec->catfile( + $base_relative, "service.$osbase.riscv64.otherpkgs.pkglist" + ); + is( + imgutils::get_profile_def_filename( $osver, 'service', 'riscv64', $base, 'otherpkgs.pkglist' ), + $otherpkgs, + "$osver riscv64 service otherpkgs resolves to the riscv64 file", + ); + like( slurp_repo_file($otherpkgs_relative), qr{^xcat/xcat-dep/rh10/riscv64/goconserver$}m, "$dir netboot service otherpkgs pulls goconserver from the riscv64 EL10 dep repo" ); + + my $install_otherpkgs_relative = File::Spec->catfile( + $share_relative, 'install', $dir, + "service.$osbase.riscv64.otherpkgs.pkglist" + ); + my $install_otherpkgs = repo_path($install_otherpkgs_relative); + ok( -r $install_otherpkgs, "install/$dir/service.$osbase.riscv64.otherpkgs.pkglist exists" ); + like( slurp_repo_file($install_otherpkgs_relative), qr{^xcat/xcat-dep/rh10/riscv64/goconserver$}m, "$dir install service otherpkgs pulls goconserver from the riscv64 EL10 dep repo" ); + like( slurp_repo_file($install_otherpkgs_relative), qr{^xcat/xcat-core/xCATsn$}m, "$dir install service otherpkgs installs xCATsn" ); +} + +# an architecture without its own files still falls back to the arch-less ones +my $rocky_base = File::Spec->catdir( $share, 'netboot', 'rocky' ); +is( + imgutils::get_profile_def_filename( 'rocky10.2', 'compute', 'riscv32', $rocky_base, 'pkglist' ), + File::Spec->catfile( $rocky_base, 'compute.pkglist' ), + 'an unknown architecture falls back to the arch-less compute pkglist', +); + +done_testing(); diff --git a/xCAT-test/unit/nodediscover_default_netboot.t b/xCAT-test/unit/nodediscover_default_netboot.t new file mode 100644 index 000000000..964268636 --- /dev/null +++ b/xCAT-test/unit/nodediscover_default_netboot.t @@ -0,0 +1,112 @@ +#!/usr/bin/env perl +use strict; +use warnings; +## no critic (Modules::RequireFilenameMatchesPackage) + +use FindBin; +use lib "$FindBin::Bin/../lib"; +use Test::More; + +use XCAT::Test::File qw(repo_path); + +# nodediscover sets noderes.netboot for a freshly discovered node when the +# admin did not pick a method that fits the reported architecture. The ladder +# lives in _default_netboot() so it can be checked without a database: stub +# the modules the plugin loads at compile time and call the helper directly. + +BEGIN { + package xCAT::Table; + sub new { + my ( $class, $name ) = @_; + return bless { name => $name }, 'Local::DiscoveryTable'; + } + $INC{'xCAT/Table.pm'} = __FILE__; + + package Local::DiscoveryTable; + our @writes; + sub getNodeAttribs { return {}; } + sub setNodeAttribs { + my ( $self, $node, $attrs ) = @_; + push @writes, [ $self->{name}, $node, { %{$attrs} } ]; + return; + } + sub close { return; } + sub commit { return; } + + package xCAT::Utils; + $INC{'xCAT/Utils.pm'} = __FILE__; + + package XML::Simple; + sub import { } + $INC{'XML/Simple.pm'} = __FILE__; + + package xCAT::data::switchinfo; + our %global_mac_identity; + $INC{'xCAT/data/switchinfo.pm'} = __FILE__; + + package xCAT::DiscoveryUtils; + $INC{'xCAT/DiscoveryUtils.pm'} = __FILE__; +} + +my $plugin = repo_path('xCAT-server/lib/xcat/plugins/nodediscover.pm'); +plan skip_all => "$plugin not found" unless -r $plugin; + +require $plugin; + +my $warnings = 0; +local $SIG{__WARN__} = sub { $warnings++; diag(@_) }; + +sub default_netboot { + return xCAT_plugin::nodediscover::_default_netboot(@_); +} + +# riscv64: UEFI + grub2 only +is( default_netboot( 'riscv64', '', '' ), 'grub2', 'riscv64 nodes default to grub2' ); +is( default_netboot( 'riscv64', undef, undef ), 'grub2', 'riscv64 defaults to grub2 when no platform or netboot was reported' ); +is( default_netboot( 'riscv64', '', 'xnba' ), 'grub2', 'a riscv64 node with an x86 method is corrected to grub2' ); +is( default_netboot( 'riscv64', '', 'grub2' ), undef, 'riscv64 keeps an explicit grub2 method' ); +is( default_netboot( 'riscv64', '', 'grub2-http' ), undef, 'riscv64 keeps grub2-http' ); +is( default_netboot( 'riscv64', '', 'grub2-tftp' ), undef, 'riscv64 keeps grub2-tftp' ); +is( default_netboot( 'riscv32', '', '' ), undef, 'riscv32 is not treated as riscv64' ); + +# existing architectures keep their behavior +is( default_netboot( 'x86_64', '', '' ), 'xnba', 'x86_64 nodes default to xnba' ); +is( default_netboot( 'x86_64', '', 'pxe' ), undef, 'x86_64 keeps pxe' ); +is( default_netboot( 'x86_64', '', 'xnba' ), undef, 'x86_64 keeps xnba' ); +is( default_netboot( 'x86', '', 'grub2' ), 'xnba', 'x86 with a non-x86 method is corrected to xnba' ); +is( default_netboot( 'ppc64le', 'PowerNV', '' ), 'petitboot', 'PowerNV nodes default to petitboot' ); +is( default_netboot( 'ppc64le', 'PowerNV', 'petitboot' ), 'petitboot', 'PowerNV nodes are always set to petitboot' ); +is( default_netboot( 'ppc64', 'pSeries', '' ), 'yaboot', 'non-PowerNV ppc nodes default to yaboot' ); +is( default_netboot( 'ppc64', 'pSeries', 'yaboot' ), undef, 'ppc nodes keep yaboot' ); +is( default_netboot( 'armv7l', '', '' ), 'onie', 'armv7l switches default to onie' ); +is( default_netboot( 'armv7l', '', 'onie' ), undef, 'armv7l keeps onie' ); +is( default_netboot( 'aarch64', '', '' ), undef, 'aarch64 is left untouched, as before' ); +is( default_netboot( undef, undef, undef ), undef, 'a missing arch sets nothing' ); + +is( $warnings, 0, 'no warnings were emitted for undefined inputs' ); + +# Discovery data are later stored key by key. Exercise the real request path to +# prove that an absent platform is not added while selecting the netboot method. +@Local::DiscoveryTable::writes = (); +my $request = { + node => ['node1'], + arch => ['riscv64'], +}; +my @callbacks; +xCAT_plugin::nodediscover::process_request( + $request, + sub { push @callbacks, @_ }, + sub { }, +); +ok( !exists $request->{platform}, 'discovery does not add a missing platform to the request' ); +ok( + scalar( grep { + $_->[0] eq 'noderes' + && $_->[1] eq 'node1' + && $_->[2]->{netboot} eq 'grub2' + } @Local::DiscoveryTable::writes ), + 'the request path applies the riscv64 grub2 default', +); +is( scalar @callbacks, 1, 'the isolated request stops at the missing client address boundary' ); + +done_testing(); diff --git a/xCAT-test/unit/riscv64_packaging.t b/xCAT-test/unit/riscv64_packaging.t new file mode 100644 index 000000000..f4f456481 --- /dev/null +++ b/xCAT-test/unit/riscv64_packaging.t @@ -0,0 +1,108 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../lib"; +use lib "$FindBin::Bin/../../build-utils/lib"; +use Test::More; + +use XCAT::Test::File qw(repo_path slurp_repo_file); +use XCAT::BuildUtils qw(targetarch_from_target); + +# riscv64 packaging: the arch-named packages (xCAT, xCATsn) must resolve their +# architecture token and dependencies for riscv64, and the build scripts must +# know the riscv64 package names. The Genesis image comes from the OpenEmbedded +# packages, so there is no arch-named Genesis package to resolve here. + +my $xcat = slurp_repo_file('xCAT/xCAT.spec'); +like( $xcat, qr/^%ifarch riscv64\n(?:#[^\n]*\n)*Requires: ipmitool-xcat >= 1\.8\.18-4\n%endif$/m, 'xCAT.spec requires ipmitool-xcat on riscv64' ); +my ($xcat_rv) = $xcat =~ /^%ifarch riscv64\n((?:#[^\n]*\n|Requires:[^\n]*\n)*)%endif$/m; +unlike( $xcat_rv || '', qr/xnba-undi|syslinux-xcat|elilo-xcat/, 'xCAT.spec does not require the x86 PXE loaders on riscv64' ); + +# riscv64 has no legacy Genesis package: the requirement must disappear rather than +# resolve to an unsatisfiable name, and the OpenEmbedded image is recommended instead. +like( $xcat, qr/^%\{\?genesistarch:Requires: xCAT-genesis-scripts-%\{genesistarch\} = 1:%\{version\}-%\{release\}\}$/m, 'xCAT.spec asks for the legacy Genesis package only where the architecture has one' ); +like( $xcat, qr/^Recommends: xCAT-genesis-openembedded-riscv64$/m, 'xCAT.spec recommends the RISC-V OpenEmbedded Genesis image' ); + +SKIP: { + my $rpmspec = qx(command -v rpmspec 2>/dev/null); + chomp($rpmspec); + skip 'rpmspec is not installed', 2 unless $rpmspec && -x $rpmspec; + + my $spec = repo_path('xCAT/xCAT.spec'); + open( my $requires_fh, '-|', + $rpmspec, '-q', '--target', 'riscv64', '--requires', $spec ) + or BAIL_OUT("unable to run $rpmspec: $!"); + my $requires = do { local $/; <$requires_fh> }; + close($requires_fh) + or BAIL_OUT("rpmspec failed for $spec with status " . ($? >> 8)); + + unlike( $requires, qr/genesis-scripts/, 'a riscv64 build requires no legacy Genesis package' ); + unlike( $requires, qr/\Q%{genesistarch}\E/, 'a riscv64 build leaves no unexpanded architecture macro' ); +} + +my $xcatsn = slurp_repo_file('xCATsn/xCATsn.spec'); +like( $xcatsn, qr/^%ifarch riscv64\nRequires: ipmitool-xcat >= 1\.8\.17-1\n%endif$/m, 'xCATsn.spec requires ipmitool-xcat on riscv64' ); +like( $xcatsn, qr/^Recommends: xCAT-genesis-openembedded-riscv64$/m, 'xCATsn.spec recommends the RISC-V OpenEmbedded Genesis image' ); + +my $server = slurp_repo_file('xCAT-server/xCAT-server.spec'); +like( $server, qr/^Recommends: perl-DB_File$/m, 'xCAT-server.spec recommends perl-DB_File on EL10 (riscv64 has no EPEL to provide it)' ); +like( + $server, + qr/^%if 0%\{\?rhel\} >= 10\nRecommends: perl-DB_File\n/m, + 'the weak perl-DB_File dependency is limited to EL10, where riscv64 has no EPEL', +); +like( + $server, + qr/^%else\nRequires: perl-DB_File\n%endif$/m, + 'build hosts without weak dependencies keep the hard perl-DB_File requirement', +); +like( + $server, + qr/^%global __requires_exclude %\{\?__requires_exclude:%\{__requires_exclude\}\|\}\^perl\\\\\(DB_File\\\\\)\$$/m, + 'xCAT-server.spec appends the generated perl(DB_File) requirement to the build root filter', +); +like( $server, qr/^Requires: perl-Net-Telnet perl-Net-DNS perl-Crypt-CBC perl-Crypt-Rijndael$/m, 'xCAT-server.spec keeps the other EL perl requires' ); + + +my $architectures = repo_path('build-utils/rpm-architectures.sh'); +is(system('sh', '-n', $architectures), 0, 'the shared build architecture data parses as POSIX shell'); +open( + my $arch_fh, + '-|', + 'sh', '-c', + '. "$1"; printf "%s\n%s\n%s\n" "$XCAT_CORE_RPM_ARCHES" "$XCAT_LOCAL_RPM_ARCHES" "$XCAT_LOCAL_COLLECT_ARCHES"', + 'sh', $architectures, +) or BAIL_OUT("unable to load $architectures: $!"); +my @architecture_sets = <$arch_fh>; +close($arch_fh) or BAIL_OUT("unable to read architecture data from $architectures"); +chomp @architecture_sets; + +is( + $architecture_sets[0], + 'x86_64 ppc64 ppc64le s390x aarch64 riscv64', + 'the release build includes riscv64 without changing its existing architecture set', +); +is( + $architecture_sets[1], + 'x86_64 ppc64 s390x aarch64 riscv64', + 'the local build includes riscv64 without changing its existing architecture set', +); +is( + $architecture_sets[2], + 'noarch x86_64 ppc64 riscv64', + 'the local build collects the riscv64 packages it produces', +); + +# buildrpms.pl derives the rpm architecture from the mock target name through +# the loadable build utility, so exercise the implementation directly. +is( targetarch_from_target('rocky-10-riscv64-xcat', 'x86_64'), 'riscv64', 'a suffixed riscv64 forcearch target resolves to riscv64' ); +is( targetarch_from_target('rocky-10-riscv64', 'x86_64'), 'riscv64', 'the stock riscv64 target resolves to riscv64' ); +is( targetarch_from_target('alma+epel-10-ppc64le', 'x86_64'), 'ppc64le', 'the ppc64le target still resolves to ppc64le' ); +is( targetarch_from_target('alma+epel-10-x86_64', 'x86_64'), 'x86_64', 'the x86_64 target still resolves to x86_64' ); +is( targetarch_from_target('opensuse-leap-15.6-x86_64', 'x86_64'), 'x86_64', 'a dashed distro name still resolves its arch' ); +is( targetarch_from_target('custom-target-foo', 'x86_64'), 'foo', 'a target without an architecture token keeps the last part' ); +is( targetarch_from_target(undef, 'x86_64'), 'x86_64', 'no target means the host architecture' ); + +done_testing(); diff --git a/xCAT/debian/control b/xCAT/debian/control index 22cf3e165..0481919e6 100644 --- a/xCAT/debian/control +++ b/xCAT/debian/control @@ -10,7 +10,7 @@ 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, 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 +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 Suggests: yaboot-xcat Description: Metapackage for a common, default xCAT setup xCAT is Extreme Cluster/Cloud Administration Toolkit. xCAT offers complete diff --git a/xCAT/xCAT.spec b/xCAT/xCAT.spec index 34cbfdf04..febac8836 100644 --- a/xCAT/xCAT.spec +++ b/xCAT/xCAT.spec @@ -54,11 +54,15 @@ Requires: xCAT-server = 4:%{version}-%{release} %if %nots390x Requires: xCAT-probe = 4:%{version}-%{release} -Requires: xCAT-genesis-scripts-%{genesistarch} = 1:%{version}-%{release} +# Only where a legacy Genesis package exists for the build architecture. riscv64 has +# none: its image ships as xCAT-genesis-openembedded-riscv64, which mknb consumes +# directly, and an unset genesistarch would otherwise emit an unsatisfiable name. +%{?genesistarch:Requires: xCAT-genesis-scripts-%{genesistarch} = 1:%{version}-%{release}} # RPM 4.11 does not recognize weak dependency tags. %if 0%{?fedora} || 0%{?rhel} >= 8 || 0%{?suse_version} >= 1500 Recommends: xCAT-genesis-openembedded-x86_64 Recommends: xCAT-genesis-openembedded-ppc64le +Recommends: xCAT-genesis-openembedded-riscv64 %endif %endif @@ -106,6 +110,14 @@ Requires: ipmitool-xcat >= 1.8.18-4 %endif %endif +%ifos linux +%ifarch riscv64 +# riscv64 management nodes manage BMC based nodes; the x86 PXE loaders are +# x86-only packages and riscv64 nodes boot through UEFI and grub2. +Requires: ipmitool-xcat >= 1.8.18-4 +%endif +%endif + %description xCAT is a server management package intended for at-scale management, including hardware management and software management. diff --git a/xCATsn/debian/control b/xCATsn/debian/control index d66ea6c13..281c92066 100644 --- a/xCATsn/debian/control +++ b/xCATsn/debian/control @@ -9,7 +9,7 @@ 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 +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 Suggests: yaboot-xcat Description: Metapackage for a common, default xCAT service node setup xCATsn is a service node management package intended for at-scale diff --git a/xCATsn/xCATsn.spec b/xCATsn/xCATsn.spec index 7540cd694..1f8d9f47d 100644 --- a/xCATsn/xCATsn.spec +++ b/xCATsn/xCATsn.spec @@ -25,6 +25,7 @@ Requires: xCAT-probe = 4:%{version}-%{release} %if 0%{?fedora} || 0%{?rhel} >= 8 || 0%{?suse_version} >= 1500 Recommends: xCAT-genesis-openembedded-x86_64 Recommends: xCAT-genesis-openembedded-ppc64le +Recommends: xCAT-genesis-openembedded-riscv64 %endif # Match xCAT-genesis-scripts package naming by build architecture. @@ -83,6 +84,11 @@ Requires: ipmitool-xcat >= 1.8.17-1 Requires: ipmitool-xcat >= 1.8.17-1 %endif %endif +%ifos linux +%ifarch riscv64 +Requires: ipmitool-xcat >= 1.8.17-1 +%endif +%endif %description xCAT supports management of very large sized cluster by creating a Hierarchical