Skip to content

feat(network): provision a node over IPv6 - #7972

Open
dhilst wants to merge 31 commits into
xcat2:masterfrom
VersatusHPC:feat/ipv6-foundation-clean
Open

dhilst wants to merge 31 commits into
xcat2:masterfrom
VersatusHPC:feat/ipv6-foundation-clean

Conversation

@dhilst

@dhilst dhilst commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

This PR adds support for provisioning a node over IPv6.

xCAT::NetworkUtils gains the address helpers the rest of the change uses: format_host_port, parse_host_port and format_uri_host bracket an IPv6 literal where a URI or a host:port needs it, and prefer_routable_addresses orders a resolver answer so a routable address wins over a link-local one.

makehosts writes an IPv6 hosts.ip and IPv6 otherinterfaces entries. makedns serves AAAA and ip6.arpa PTR records. The Kea backend writes a DHCPv6 subnet whose reservation carries an RFC 5970 bootfile-url. An IPv6-only node is kept out of the DHCPv4 configuration.

Four defects were found by running the result on a management node rather than by reading it, each fixed in its own commit. site.master took the management node's link-local address, because getipaddr returns the first resolver entry; that needed two commits, since getipaddr has two resolver paths and the first fix ordered only one of them. makedhcp refused a network whose mask column holds /64, and reported an unresolvable tftpserver for an IPv6 boot server.

Out of scope here: an IPv6-only provisioning network end to end, a service-node hierarchy over IPv6, dual-stack selection policy, and BMC configuration over IPv6.

Fixes #7309

viniciusferrao and others added 16 commits October 21, 2026 18:59
An IPv6-only provisioning network cannot deploy a node. The discovery and
credential callbacks open IPv4 sockets, the kickstart and boot templates
render an IPv4 address into every endpoint, and makehosts drops an IPv6
value because hosts.pm gates on isIpv4addr.

NetworkUtils gains the address helpers the rest of the change uses:
format_host_port, parse_host_port and format_uri_host bracket an IPv6
literal, getNodeNetworkCfg6 reads the static IPv6 configuration of a node,
my_ip_facing_family selects the management-node address of one family,
getIPv6ReverseZone builds a nibble-aligned reverse zone, and
node_is_ipv6_only and ipv6_server_for_node answer the two questions the
plugins used to answer for themselves. credentials.pm opens the callback
socket through IO::Socket::INET6 when the modules are present and keeps the
IPv4 path when they are not. Template.pm and anaconda.pm render an IPv6
kickstart network line and bracketed endpoints. The postscripts and the
stateless xcatroot format every endpoint through the bracketing helpers.

xCAT-server requires perl-IO-Socket-INET6 and perl-Socket6 on EL, and
libio-socket-inet6-perl and libsocket6-perl on Debian.

(cherry picked from commits c0a69bb and caa8ca7)
Ported onto upstream master ee6b4f5. The two commits are folded because the
second refactors the first. Master is kept where it has a newer form of the
same thing: the getipaddr cache guard, the Kea control_socket_path helper, the
netboot method list, install_device_params, the modern_installer test and the
isValidIp validator, which replaces the isValidIPAddress this branch added.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit ef695d3624a0b4c37cac0f0cac2bbbb9d2a1e4df)
Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit d93786c)
Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit c22a8c1)
Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit ba57cae)
Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit 16cffc6)
Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit 5d59c4793b6a91b2bc1dd3e77e8f47ea7ba6e2bd)
Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit 33707815b1f45dcb8d0f0dbd0c0d48da7cc9e39b)
Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit 3df86595a3e0e2a9e2b6d2e30ba3e4a5c4e5cf3a)
Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
The ported branch predates the Kea control-socket detection, the iPXE netboot
method and the isValidIp validator that master now carries, so seven unit
files failed: six on the isValidIPAddress name the branch added beside
master's isValidIp, and netboot_options_ipv6.t on the netboot method list.

Every caller now uses isValidIp. netboot_options_ipv6.t no longer names a
method list: it reads the netboot value each node template ships, and asserts
that lookupNetboot offers it for that architecture and that a plugin of that
name exists. The Kea renderer loses the control_socket_name assertions, which
expected a socket basename; control_socket_path reads the real runtime
directory out of the Kea build report instead. getipaddr keeps one address per
host, so the cache assertion now states the invariant master implements: an
IPv6 cache entry is never served to an OnlyV4 caller.

kea_node_reservations resolves the next server on first use, because
next_server_for_node reports an unresolvable tftpserver and an IPv6-only node
leaves the loop before it needs one. kea_sync_node_client_classes skips an
IPv6-only node before the mac column is read, so a *NOIP* alias on such a node
no longer produces a DHCPv4 DROP class, while the same alias on an IPv4 node
still does.

network_utils_ip_validation.t is renamed networkutils_ipv6_helpers.t, one
character away from master's networkutils_ip_validation.t being too close to
tell apart.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
Seven of the ported unit files drive shell: they run bash -n on a product
script, cut a function out of it with a regex and eval the copy, or write a
fake executable into PATH. A .t runs Perl in process, so each one tested its
own scaffolding as much as the product, and a reformat of the script would
break it while a move of the code would not.

The five bats files replace them and use the existing shell_source helper.
ipv6_endpoint_helpers drives the four separate copies of xcat_endpoint_host
from one table, because the scripts carry a copy each -- xcatroot runs in the
dracut initrd, xcatpostinit1.netboot before /xcatpost exists, xcatinstallpost
in the installer root -- and the copies must agree.
ipv6_xcatdsklspost_endpoints uses XCATDSKLSPOST_SOURCE_ONLY to load the real
parsehttpserver and format_host_port and asserts a bracketed authority
survives a round trip. ipv6_syslog_forwarding covers the rsyslog bracket and
the syslog-ng udp6 driver. ipv6_configeth_install_nic drives
discover_install_nic_ipv6 against recorded "ip -6 addr show" output and pins
that a temporary, tentative, deprecated or dadfailed address is never chosen.
ipv6_updateflag_awk runs updateflag.awk against a loopback listener of each
family and pins the bracket stripping.

Each assertion was run against the unfixed script and fails there. Two gaps
stay: allowcred.awk has no coverage, and the /inet6/ prefix updateflag.awk
chooses cannot be covered, because gawk 5.3.0 reaches an IPv6 literal through
/inet/ as well.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
… node

On an EL10 management node whose own name resolves to both families,
xcatconfig writes a link-local IPv6 address into site.master and
site.nameservers. Measured on an AlmaLinux 10.2 node: getent reports
fe80::4054:ff:fec9:9ad2%2 before 192.168.201.154, and
xCAT::NetworkUtils->getipaddr($hostname) answers
fe80::4054:ff:fec9:9ad2%mgmt0, which xcatconfig stores. A node on another link
cannot reach that address, and the %zone form is not a literal isIpv6addr or
any consumer of site.master accepts.

getipaddr resolves through Socket::getaddrinfo with AF_UNSPEC when no family is
asked for, and returned the first entry getaddrinfo gave. prefer_routable_addresses
orders address literals so a routable one comes before a link-local one and
removes the zone identifier, and _addrinfo_routable_first applies the same order
to the getaddrinfo entries before getipaddr reads the first. A link-local
address is still returned when the host has no other.

networkutils_linklocal_preference.t covers the order, the zone identifier and
both link-local ranges. Removing the routable split fails 4 of its 10
assertions, removing the zone strip fails 3, and treating 169.254.0.0/16 as
routable fails 1.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
getipaddr reaches the resolver through Socket6 when that module is present and
through core Socket otherwise, and only the core path ordered its results. A
management node with Socket6 installed therefore still answered
fe80::4054:ff:fec9:9ad2%mgmt0 for its own name, which is what xcatconfig wrote
into site.master. Measured on an AlmaLinux 10.2 node with perl-Socket6: the
previous commit was installed and getipaddr returned the link-local address
unchanged.

_socket6_addrinfo_routable_first reorders the flat five-element records
Socket6::getaddrinfo answers, by the same rule the core path uses. A list whose
length is not a whole number of records is returned untouched.

networkutils_linklocal_preference.t covers the record reorder with a stubbed
Socket6::getnameinfo. Making the link-local test constant false fails 2 of its
assertions. The call site itself has no unit test; the cluster run is what
shows site.master routable.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
makedhcp -n answers "IPv6 network 2001:db8:7309:: must include a prefix length
for Kea DHCPv6" and writes no DHCPv6 config for a network whose mask column
holds /64. Measured on an AlmaLinux 10.2 management node: the network was
defined with mask=/64 and makedhcp -n exited 1, leaving /etc/kea/kea-dhcp6.conf
as the example file the Kea package ships.

kea_subnet6_intent in dhcp.pm parsed the mask itself with /^(\d+)$/, so it took
64 and refused /64. NetworkUtils::getIPv6PrefixLength already reads both
spellings, from the net value or from the mask column, and is what the rest of
the IPv6 code uses. kea_subnet6_intent now calls it, which removes the second
parse of the same field.

dhcp_kea_plugin_intent.t drives kea_subnet6_intent with both spellings and
asserts each yields the /64 subnet. Against the unfixed tree the /64 case fails
two assertions. A third assertion holds the refusal for a network that names no
prefix at all, matched on the message rather than on the error being set.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
…IPv6

makedhcp answers "Error: Unable to resolve the tftpserver for node" for a node
whose tftpserver or xcatmaster is an IPv6 address, and exits non-zero. Measured
on an AlmaLinux 10.2 management node with tftpserver=2001:db8:7309::1: the
DHCPv6 reservation and its bootfile-url were written correctly and makedhcp
still reported the error.

next_server_for_node reads both attributes with inet_aton, which cannot parse
an IPv6 literal, and treats the failure as a misconfiguration. An IPv6 boot
server is named in the DHCPv6 reservation rather than in a DHCPv4
next-server, so there is no DHCPv4 answer due and nothing to report. Both
branches now return without a value when the attribute is an IPv6 address.

A name that does not resolve is still reported, which is the behaviour the new
assertions would otherwise be satisfied by removing. dhcp_kea_plugin_intent.t
covers all three: removing either guard fails the matching "reports no error"
assertion, and removing the error path fails the assertion that names the
attribute.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
buildrpms.pl and builddebs.pl write their output into the checkout: dist/, the
generated pod and html man pages under perl-xCAT and the per-package share
directories, and two pod2htmd.tmp files. None of them is ignored, so a build
leaves a dirty tree and "git add -A" after one sweeps about 500 generated
files into a commit.

.gitignore names them. The per-package debian substvars are left alone,
because they are already tracked.

This is unrelated to the IPv6 change it arrives with and can be dropped or
split out.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
@dhilst dhilst added this to the 2.20 milestone Oct 22, 2026
@dhilst dhilst self-assigned this Oct 22, 2026
dhilst added 12 commits October 22, 2026 09:37
site.master takes the value getipaddr returns for the management node name.
A management node resolves its own name to its link-local address as well,
and a node on another link cannot reach fe80::/10. Two commits on this branch
order the resolver answer so a routable address comes first, one for each of
the two resolver paths getipaddr has.

Neither order had a test that can fail. networkutils_linklocal_preference.t
calls the two ordering helpers as pure functions and never calls getipaddr,
so deleting either call in getipaddr left all 298 unit files passing.

Add one file per resolver path. Each stubs the resolver at the Socket or
Socket6 level, hands getipaddr an answer that lists a link-local address
first, and asserts on what getipaddr returns. The Socket6 file carries a
Socket6 stub in %INC, so it measures that path on a host with no Socket6,
which is the condition the unit suite runs in.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
makehosts reads the address test in four places. The branch widened all four
from isIpv4addr to isValidIp so an IPv6 hosts.ip reaches /etc/hosts, and only
addotherinterfaces had a test. Narrowing add_hosts_content, delotherinterfaces
or the no-noderange path back to isIpv4addr left the whole unit suite passing,
so three of the four changes measured nothing.

The no-noderange path had no sub to call: its loop sat inside process_request,
which opens /etc/hosts and takes a lock. Move that loop to
add_all_hosts_content, which takes the hosts table rows and answers the node
names, and leave the table read, donics and writeout in process_request. The
domain carry-over between rows is kept as it was.

makehosts_ipv6.t now drives each of the three remaining places with an IPv6
address and reads the host entries it produced. Each one fails on its own
assertion when its address test is narrowed back to isIpv4addr.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
Every caller of Socket6 and IO::Socket::INET6 loads it with an eval and keeps
working on IPv4 when the load fails. NetworkUtils carries a second resolver
path on core Socket for exactly that case. xCAT-server.spec and
xCAT-server/debian/control named both modules as a hard dependency, which
states a dependency the code does not have and stops xCAT installing where
the modules are missing.

Declare both as Recommends in the spec and in debian/control. A package
manager installs a recommendation by default, so a management node still gets
them. Both are in EL AppStream and in openEuler 24.03 everything, measured
from its primary.xml.

The family cache block in networkutils_ipv6_helpers.t was guarded on Socket6
and skipped where the unit suite runs, which has no Socket6. The family cache
keys are the same on both resolver paths, so the guard now accepts either
resolver: six more assertions run, and one of them fails when the OnlyV4 and
OnlyV6 cache split is removed.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
Sixteen assertions on this branch matched a regular expression against the
source of the code they test. Such an assertion passes when the code is moved
somewhere it never runs, and fails when the file is reformatted and nothing
has changed: a perltidy respace of updatenode.pm:1606 turned
updatenode_ipv6_endpoint.t red with the behaviour unchanged.

updatenode_ipv6_endpoint.t now runs updatenoderunps and updatenodesoftware
and reads the xdsh command each one dispatched. It also covers the service
node name that has no usable endpoint, which the regular expressions could
not reach.

anaconda.pm built the debug syslog kernel arguments inline inside two large
subs. Move each to its own sub, _netboot_syslog_kcmdline_args and
_install_syslog_kcmdline_args, and render them in the test. An unusable
server name now gives no syslog arguments, where it gave "syslog.server="
with nothing after it.

pping is a program with no sub API, so the nmap and fping command building
moves to xCAT::DSHCore as nmap_ping_command and fping_ping_command, and pping
stays the caller that forks and execs the list.

credentials_ipv6_callback.t read allowcred.awk as text, and ran gawk from a
.t. The awk program is covered by xCAT-test/bats/ipv6_allowcred_awk.bats,
which starts it and speaks the credential protocol to it over both families.
Stubbing Socket6 and IO::Socket::INET6 in %INC also unskips the four
_open_callback_socket assertions, which skipped where the suite runs.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
Two gaps from the same branch.

ddns.pm:453 assigned "my @myips = _local_addresses_for_network(...)" and never
read the value, and _local_addresses_for_network had no other caller. The
routine and its three assertions in ddns_ipv6.t are removed: a test on a
routine nothing calls measures nothing and keeps dead code alive.

The commit that moved the shell tests to bats deleted el10_ipv6_runtime.t and
wrote no bats file for pre.rhels10, so its new create_listen_socket, its peer
allow list, its screendump rewrite and extract_url_host had no assertions.
ipv6_pre_rhels10.bats renders the install monitor pre.rhels10 embeds, fills
the two substitution tokens the way xCAT fills them, runs it and speaks its
protocol over each family. It also drives extract_url_host and the expression
that finds the install URL in the kernel command line, read from the script
rather than retyped.

Six reversions of the script were each measured: an AF_INET-only listener, a
missing peer check, the os.popen screendump, the colon split of the URL, a
grep that drops the bracket alternative, and a missing strip on the received
command. Every one fails at least one assertion.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
…ries

addnode and delnode find an existing host entry through the host-entry index,
and that index indexed node names only for an IPv4 first field and keyed the
address on its literal spelling. So makehosts left two entries for one node:
changing node01 from 2001:db8::10 to 2001:db8::20 added the second address and
kept the first, and an expanded IPv6 entry stayed when the compressed spelling
of the same address was added or deleted. The node resolved to a stale address.

_host_entry_matches handled both cases and no code called it. It is removed,
because a second place that compares an address is how this file reached this
state.

xCAT::NetworkUtils gains canonical_ip_address, which answers the one spelling
every spelling of an address shares. It is built on _pack_ip_address, so it
prefers core Socket and falls back to the optional Socket6. The index keys on
that answer, and it indexes node names for any first field whose shape is an
address.

On a host with no parser for IPv6 the canonical form is undefined and the
index keys on the literal. An address change written the same way still leaves
one entry, through the node name. An equivalent spelling becomes a second
entry, which is the limit of what such a host can decide.

makehosts_ipv6.t now runs addnode and delnode and reads the host entries they
produced. Eight of its 21 assertions fail against the previous index,
including the address change and both spelling-equivalence cases.

makehosts_index.t stubbed xCAT::NetworkUtils as an empty package. It loads the
real module, because the index now reads the canonical address from it.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
dracut dispatches the network root in 45net-lib/netroot.sh with
handler=$(command -v "${netroot%%:*}root"). command -v skips a file
without the execute bit, so an xcatroot shipped mode 0644 makes the node
print "No handler for netroot type 'xcat'" and power off in its
initramfs. genimage copies the file with the mode it reads from the share
tree, and dracut's inst_script keeps that mode, so the mode in the
repository is what reaches the node.

The new test asserts the execute bit on every xcatroot under
xCAT-server/share/xcat/netboot and xCAT-genesis-base. It fails on the
current tree, on
xCAT-server/share/xcat/netboot/rh/dracut_105/stateless/xcatroot.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
An EL10 stateless compute node gets its lease, loads its iPXE script and
downloads the kernel and the initramfs, then dies four seconds into the
kernel:

    dracut: FATAL: No handler for netroot type 'xcat'
    dracut: Refusing to continue
    reboot: Power down

xCAT-server/share/xcat/netboot/rh/dracut_105/stateless/xcatroot lost its
execute bit in 3928c04. genimage copies the file with the mode it reads
from the share tree, and dracut's inst_script keeps that mode, so
/sbin/xcatroot reaches the initramfs mode 0644. dracut resolves the
handler with command -v, which skips a file that is not executable, so
netroot.sh finds no handler and gives up. An EL9 node reads dracut_047,
where the same file is mode 0755, so only EL10 is affected.

The fix restores mode 0755. netboot_xcatroot_executable.t fails without
it.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
…e an IPv6 literal

site.master may carry a port. format_syslog_forward_target in
xCAT/postscripts/syslog bracketed any value containing a colon, so
MASTER=127.0.0.1:1515 rendered the rsyslog line as @[127.0.0.1:1515], which
rsyslog does not accept, and syslog_ng_udp_driver chose udp6 for an IPv4 peer.
format_syslog_server in the dracut_105 rsyslogd-start.sh carries its own copy of
the rule and had the same defect; it runs inside the initramfs and cannot source
the postscript, so the rule is held down twice on purpose.

An IPv4 address or a hostname with a port has exactly one colon and an IPv6
literal has at least two, so the test is now on two colons rather than one. A
value already starting with "[" is left alone whether or not a port follows it,
which the old pattern missed: it required a trailing "]" and so double-bracketed
[2001:db8::1]:514.

ipv6_syslog_forwarding.bats gains four cases. Without the fix, "a target with a
port is not mistaken for an IPv6 literal", "an already-bracketed target with a
port is left alone" and "the initrd launcher does not mistake a port for an IPv6
literal" all fail; the compressed-literal case passes either way and is there to
show the bracketing of ::1 did not regress.

Also: grub2_ipv6.t could not load grub2.pm once xcat-core/xcat2#7980 made it use
xCAT::DHCP::BootPolicy, which lives in perl-xCAT. The file died at compile time
and reported no plan at all, which in a prove summary reads as a count
discrepancy rather than a failure. It now puts perl-xCAT and the server perl
directory on @inc, as every other plugin test does.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
A node with an IPv6 reservation took a pool address with no bootfile-url and
stopped at PXE-E21. RFC 6355 carries the sixteen octets of the system UUID as
the firmware holds them, and SMBIOS 2.6 and later store time_low, time_mid and
time_hi_and_version little-endian, while vpd.uuid holds the canonical spelling
Linux reports in product_uuid. kea_duid_from_uuid copied that spelling verbatim,
so the DUID disagreed with the client in its first eight octets and Kea never
matched the reservation.

Measured on xcat76-cn, 2026-10-23, with vpd.uuid
c78c76bf-3286-46f3-b80d-8fe079dd6de7:
  xCAT wrote        00:04:c7:8c:76:bf:32:86:46:f3:b8:0d:8f:e0:79:dd:6d:e7
  the firmware sent 00:04:bf:76:8c:c7:86:32:f3:46:b8:0d:8f:e0:79:dd:6d:e7

The first three fields are now reversed. The ISC path had its own copy of the
same transformation and the same defect; it calls the sub instead, so the two
backends cannot disagree about what a client's DUID looks like. A uuid that is
not sixteen octets now has no DUID and the node is skipped with a warning,
rather than a malformed string reaching omshell.

Two assertions in dhcp_kea_plugin_intent.t held the canonical spelling and so
agreed with a DUID no firmware presents; they now hold the reversed form. Five
assertions fail without the reversal.

Also: makedhcp exited 1 on an IPv6-only node whose DHCPv6 reservation and
bootfile-url it had just written correctly. kea_node_client_classes_for_nodes
resolved next_server_for_node before the per-MAC loop that skips an IPv6-only
node, so the node still reached the IPv4 resolver, and with noderes.xcatmaster
naming a host that has only an AAAA record inet_aton fails and the sub reports
an unresolvable tftpserver with errorcode 1. 6f171a2 made the same call lazy
in kea_node_reservations and missed this second site. It is resolved on first
use now, and the new assertion is on the CALL rather than the exit code, because
the error is what the exit code is made of.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
This branch is titled "provision a node over IPv6" and shipped one IPv6
xcattest case, makenetworks_ipv6_route_filter_unit, which is a unit-style check
that no bundle names. Nothing ran against a management node to show that the
DHCPv6 path works.

makedhcp_dhcpv6_boot_intent builds its own state on the management node -- an
IPv6 address on the provisioning NIC, a matching xCAT network, and a node that
answers only on IPv6 with a fixed vpd.uuid -- and asserts four things a UEFI
client depends on: makedhcp exits 0, kea-dhcp6 accepts the configuration it
wrote, the reservation's DUID is the uuid with its first three fields reversed
as the firmware sends them, and the bootfile-url is the RFC 5970 form with a
bracketed authority. It then asserts the IPv4 configuration carries no
reservation and no client class for that node.

Every value is read out of Kea's JSON with python3 and compared for EQUALITY. A
grep for bootfile-url passes on the key alone, and a check whose pattern appears
in its own command cannot fail.

The uuid is fixed rather than generated, because the expected DUID is derived
from it. The case pre-cleans before it builds: xcattest stops a case at its
first failed check, so the cleanup at the end does not run after a failure, and
without the pre-clean a failed run would leave an address, a network and a node
behind for the next run to measure.

It needs no compute node and no image, so it runs wherever a management node is
installed. ci/bundles/ipv6_dhcpv6.bundle and the el10-x86_64-ipv6 cell in
xcat-core-ci-cd dispatch it.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
@viniciusferrao
viniciusferrao self-requested a review October 23, 2026 18:38
viniciusferrao
viniciusferrao previously approved these changes Oct 23, 2026

@viniciusferrao viniciusferrao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@dhilst
dhilst enabled auto-merge October 23, 2026 19:45
Master gained d9936e4, which moves the validation of the node
architecture, of the grub2 binary and of the node address in
xCAT_plugin::grub2::setstate above the find/rm that replaces the
configuration, and declares $serverip and $grub2protocol before that
validation block. This branch rewrites the same region to resolve the
boot server in either address family.

Resolution. The helper call grub2_server_for_node assigns the $serverip
that master now declares in the outer scope. A "my" on that assignment
compiles and passes perl -c, but it leaves the outer $serverip undef and
the configuration gets no "set root=" line; grub2_ipv6.t fails 2 of its
22 assertions on that form. The IPv4/IPv6 split of getipaddr moves to
master's hoisted validation site, so an IPv6-only node passes validation
before the previous configuration is removed.

The IPv6 boot file URL in dhcp.pm builds the loader name itself and so
bypasses grub2_node_loader, which xcat-core/xcat2#7980 added as the one place
that name is chosen. An aarch64 node over IPv6 therefore gets a name
without the .efi suffix its firmware requests. The next commit routes
that path through the helper.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
…ut .efi

Some aarch64 UEFI firmware ignores a boot file name without the .efi
suffix and requests grubaa64.efi instead. xcat-core/xcat2#7980 made
xCAT::DHCP::BootPolicy->grub2_node_loader the one place that name is
chosen, and the ISC and DHCPv4 paths of makedhcp call it.

kea_bootfile_url6_for_node in dhcp.pm builds the name itself, as
"grub2-$node", so the RFC 5970 bootfile-url of an aarch64 node never
carries the suffix and the node loads nothing. The routine also had no
node architecture to decide on: kea_build_node_reservations6 does not
read the nodetype table.

kea_build_node_reservations6 now reads nodetype.arch and noderes.tftpdir,
kea_node_reservations6 passes the nodetype entry on, and
kea_bootfile_url6_for_node asks grub2_node_loader for the name.

dhcp_grub2_node_loader.t, which holds the name down for the other two
paths, now asserts the DHCPv6 URL over its whole case table. Without this
change 2 of its 62 assertions fail: the two aarch64 cases where nodeset
has published the .efi link.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Native IPv6 support

2 participants