mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-10-12 08:12:33 +00:00
9cae1ea9f84c836fbdd13d61e54f9d3fb79c8a67
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a64dfe149a |
fix(dhcp): stop handing an installed node a boot file on either backend
A node that has finished installing has chain.currstate "boot", and from
that point the server must stop naming it a boot file: a node given a
netboot script every time it powers on reinstalls itself forever, and does
so silently, because each individual boot looks like a successful one.
Neither backend did that. The wire suite found both.
ISC gated the whole boot-from-disk branch on $doiscsi, so the rule only
fired for a node with an iscsi table row. An ordinary installed node fell
through to the netboot branches below and its xNBA second stage was handed
its own install script. Both the xnba and the pxe branch had it; the pxe
one handed out pxelinux.0.
Kea reads "boot-file-name": "" as *unspecified*, not as "no boot file", so
the empty boot file on the node's reservation did not outrank the classes
every client shares. The always-evaluated xcat-bios class supplied
xcat/xnba.kpxe, the loader came back announcing user class xNBA, and the
per-network class then supplied the network's boot script -- so withholding
only the per-node script withheld nothing. The fix is the same mechanism the
DROP and NOIP classes already use: a xcat-localboot class holding the MACs,
and a "not member('xcat-localboot')" guard on every class that names a boot
file. Kea requires a class to be defined before it is referenced, so
xcat-localboot is written first.
iSCSI is the exception on both backends. Its root disk is on the network and
gPXE is what attaches it, so it still gets a loader: ISC keeps its $doiscsi
branch, and Kea leaves those MACs out of the class.
spec.md S-31 is rewritten to say what correct is -- no boot file at all, on
either request of an xNBA boot, whatever the netboot method -- rather than
the weaker "not its own script", which the second stage above shows is not
the same statement.
conf/localboot.conf asserts the stronger form and gains a netboot=pxe node,
since that branch is written separately and the xnba node passing says
nothing about it. dhcpfixture.sh defines it.
An assertion with != or not-in now holds against a target that is absent
from the reply: a reply naming no boot file has certainly not named the
node's install script. Every other operator still fails on absence, and
"absent" remains the way to assert absence itself.
|
||
|
|
72bae42096 |
test(dhcptest): write "no boot file" as absent, which is what a reply carries
Four scenarios asserted `bootfile matches ^$` for the cases where the point is that the client is handed nothing to fetch: OPAL-v3 and s390x, which get a conf-file instead, a petitboot node, and the architecture whose loader was taken off disk. That assertion cannot pass. A server naming no boot file sends an empty BOOTP file header and no option 67, and an empty value is absent -- so the run reported "bootfile is not present in the reply" against both backends, in every case, whatever the server had done. Four failures that said nothing about either implementation. `bootfile absent` is the assertion those scenarios wanted. A unit test pins it, so the unsatisfiable form cannot come back unnoticed. |
||
|
|
2abcec617b |
test(dhcptest): assert the spec scenarios that had no wire case at all
The previous commit deleted the scenarios nothing ran. This one covers the
other half of the same review: places where spec.md states a behaviour and
nothing on the wire ever checked it.
Eight scenarios, each with the fixture work it needs:
S-36/37/38 next-server follows a node's own tftpserver, then its
xcatmaster, then the subnet -- three nodes, three sources,
three different answers, which is the only arrangement in
which a server that ignores the node attributes can be told
apart from one that honours them
S-08 a node whose mac attribute names two ports is reserved on
both, at the two addresses its two hostnames resolve to
S-33/34 a diskless node is told its iSCSI target: option 17 for a
normal client, the ISAN vendor space for one announcing that
vendor class
S-12 an architecture whose loader is not on disk is served an
address and no boot file, with a second architecture whose
loader is still there as the control
S-14 a URL names the port the web server actually listens on
S-05 a dynamic range written as a CIDR block serves out of it
S-59 a withdrawn reservation stops being handed out
S-54 a client that speaks BOOTP and not DHCP is still served
Three of these needed the tool to grow: BOOTP has no option 53, so a
BOOTREQUEST step type and a BOOTREPLY message type; option 119 is a name
list that may be compressed against itself, so RFC 3397 decoding, with a
fall back to hex on anything malformed rather than a misreading to assert
against.
RELEASE exposed a state-machine bug on the way. A step that expects no
reply left the client where it was, which is right for a DISCOVER nobody
answered and wrong for a RELEASE, which is never answered and whose whole
point is giving the lease up. A scenario could not boot a node, release
and boot it again -- which is exactly how S-02 has to be asserted, since
a session lasts one scenario and a second boot cannot reference the first
across that boundary.
Every new conf is reachable: three cases0 entries drive the eight fixture
subcommands, so none of them is dead on arrival.
|
||
|
|
f2112dcafa |
test(dhcptest): drive xCAT-generated DHCP config from the wire
dhcptest could already assert on what a server puts on the wire, but on a
single-node management node -- the CI runner above all -- there was nothing
for it to talk to, so a wire case would have passed while proving nothing.
dhcpfixture.sh builds the missing network out of a veth pair: the server end
carries the management address and is the only interface named in
site.dhcpinterfaces, the client end has no address, which is the state a
provisioning NIC is in when a machine boots on it. It then defines a network
with a dynamic range and one node with a MAC, runs makedhcp so the
configuration comes from xCAT rather than from hand-editing, and restores
everything from a trap. All the xCAT knowledge lives there, next to the
xcattest cases, so dhcptest itself stays agnostic to it.
The new scenarios assert what a provisioning server has to get right:
provision-vs-discovery.conf a known machine gets its reservation and its
own loader, an unknown one gets a pool address
discovery-bootfile.conf an unknown machine is told what to boot too
node-specific-second-stage.conf the chainloaded stage names its host
hierarchy-dhcpserver.conf a subnet whose pool was handed to another
server ignores unknown MACs but still points
known ones at it
Those are four files rather than four scenarios in one because they describe
server configurations that contradict each other, and because --set values are
resolved when a file is loaded, before -s selects anything: an unused
scenario's missing variable would otherwise abort a run that never intended to
use it. ISC dhcpd leaving the unknown-client boot file to the per-host blocks
while Kea puts it on the subnet is exactly such a contradiction, which is why
discovery-bootfile.conf is opt-in and the fixture only asks for it on Kea.
dhcptest_backend_switch reruns the same files after switching site.dhcpbackend
and regenerating, so the backend-agnosticism the tool exists to demonstrate is
itself under test rather than asserted in a comment.
The `in` operator now accepts an inclusive first-last range as well as a CIDR
and a list: a dynamic pool is written as two addresses because it rarely lines
up on a prefix boundary.
Verified against a real dnsmasq in a network namespace: 17/17, 3/3 and 5/5
assertions green, a deliberately wrong expectation failing with expected and
received on one screen, and host networking untouched.
Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
|
||
|
|
008f83d2e4 |
test(xCAT-test): add dhcptest, a wire-level DHCP client test tool
xCAT's DHCP behaviour is tested from two directions today, neither of which
touches the wire: xCAT-test/unit/dhcp_*.t assert on generated config text, and
xCAT-test/integration/dhcp_{isc,kea}_config_validation.t feed that config to a
real daemon and check it parses. Both stop at "the server accepted our
config". Nothing verifies that a client sending option 93 = 0x000b actually
gets an aarch64 loader back.
The one existing wire tool, xCAT-probe/subcmds/detect_dhcpd, hand-packs a
fixed DISCOVER carrying no option 60, 77 or 93 (so it never reaches any arch
branch), parses replies by regexing tcpdump output, and binds an
IO::Socket::INET to a local address, so it cannot run on a provisioning NIC
that has no address yet.
dhcptest fills that gap. It is a Python 3 tool using only the standard library
plus Scapy, speaking raw Layer 2, driving DHCP transactions declared in INI
files and asserting on what came back:
dhcptest run -i eth1 --set net=10.0.0.0/24 conf/full-lease.conf
dhcptest validate conf/*.conf # no root, no network, no scapy
dhcptest list conf/*.conf
dhcptest discover -i eth1 # ad-hoc, no conf file
Scenarios cover DISCOVER/OFFER, the full lease, renew and rebind, reserved
versus pooled addresses, deliberate silence, the PXE architecture matrix and
the iPXE user-class split. Assertions are a small DSL -- target, operator,
value -- over message type, BOOTP header fields, options by number or name,
subnet membership, and the boot file a client would actually use (option 67
or the header, since servers differ on which they fill).
Two boundaries are enforced by unit test rather than by convention:
- The tool is implementation-agnostic. It exchanges packets and checks
fields; it never reads the xCAT database, never runs an xCAT command, and
names no DHCP implementation. Anything that differs between servers is
expressed by whoever writes the .conf.
- Only runner.py imports scapy, so validate and list stay usable in CI on a
host with neither scapy nor root.
Host networking is never modified. The client MAC defaults to a synthetic
locally-administered address, everything goes over a raw L2 socket, no leased
address is ever configured, and the ARP responder answers only for addresses
the session was actually granted.
Variables in .conf files are configparser's own BasicInterpolation, %(name)s,
fed from [vars] and --set. $offer.address and friends are resolved separately
at step-execution time, because they name a reply that has not arrived when
the file is read; BasicInterpolation gives $ no meaning, so the two coexist
without escaping.
Packaging and CI:
- xCAT-test.spec installs dhcptest alongside unit/ and integration/, and
only recommends python3-scapy: it lives in EPEL on EL, and a hard Requires
would make xCAT-test uninstallable on a management node without EPEL.
The deb depends on it outright.
- github_action_xcat_test.pl runs the unit tests and `dhcptest validate`
last, after the fast regression.
- autotest/testcase/dhcptest/cases0 carries the two offline cases plus a
wire case that stands down with an explanation when no provisioning NIC
is available.
Verified against a real DHCP server in a network namespace: 34 lease, renew,
rebind, reservation and silence assertions and 22 PXE/iPXE assertions all
pass, and host networking is untouched before and after.
Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
|