From 255c6a6b76e814e979ef973070ac87c6422c608f Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Thu, 3 Sep 2026 14:41:13 -0300 Subject: [PATCH] test(dhcp): makedhcp -q hides a dhcpd.conf read failure and loses InfiniBand addresses The static host query reads dhcpd.conf when no configuration is in memory. When the read fails the query returns nothing, and listnode reports "no DHCP reservation found" -- the answer for a node that has no reservation. The operator cannot tell the two apart. The same query only reads a "hardware ethernet" line. An InfiniBand node declares "hardware infiniband", so its query answer carries no hardware address. A twin declaration inside the same markers must not replace the primary one either. The new assertions drive the writer to build both InfiniBand shapes, set the path of dhcpd.conf to a file that does not exist, and call listnode. A deletion that names a hostname is asserted to keep the other declarations of the node. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com> --- xCAT-test/unit/dhcp_isc_static_host_query.t | 93 +++++++++++++++++++++ xCAT-test/unit/dhcp_static_host_markers.t | 18 ++++ 2 files changed, 111 insertions(+) diff --git a/xCAT-test/unit/dhcp_isc_static_host_query.t b/xCAT-test/unit/dhcp_isc_static_host_query.t index c7fa2b795..fc193fe5b 100644 --- a/xCAT-test/unit/dhcp_isc_static_host_query.t +++ b/xCAT-test/unit/dhcp_isc_static_host_query.t @@ -7,6 +7,7 @@ use lib "$FindBin::Bin/../../xCAT-server/lib"; use lib "$FindBin::Bin/../../xCAT-server/lib/perl"; use lib "$FindBin::Bin/../../perl-xCAT"; +use File::Temp qw(tempdir); use Test::More; $ENV{XCATCFG} ||= 'SQLite:/tmp'; @@ -111,4 +112,96 @@ my ($c1name, $c1ip) = xCAT_plugin::dhcp::_query_isc_static_host('compute-01', @s is($c1ip, 'ip-address = 192.168.201.12', 'querying "compute-01" returns its own block'); +# An InfiniBand node declares "hardware infiniband". Build the block with the writer, so +# the query reads what makedhcp writes rather than a hand-made copy of it. +my @ib_config; +xCAT_plugin::dhcp::_add_isc_static_host( + 'ibnode', 'ibnode', + 'ff:00:00:00:00:00:02:00:00:02:c9:00:00:02:c9:03:00:0a:6f:ba', + 32, 'ib0', '192.0.2.20', '', 1, \@ib_config, +); +my ($ibname, $ibip, $ibhw) = + xCAT_plugin::dhcp::_query_isc_static_host('ibnode', @ib_config); +is($ibip, 'ip-address = 192.0.2.20', + 'an InfiniBand node reports the address of its reservation'); +is($ibhw, + 'hardware-address = ff:00:00:00:00:00:02:00:00:02:c9:00:00:02:c9:03:00:0a:6f:ba', + 'an InfiniBand node reports the hardware address of its reservation'); + +# An Ethernet node on an InfiniBand interface gets a twin declaration inside the same +# markers. The query must answer with the primary declaration. +my @twin_config; +xCAT_plugin::dhcp::_add_isc_static_host( + 'twinnode', 'twinnode', 'b8:3f:d2:4a:68:aa', 1, + 'ib0', '192.0.2.21', '', 0, \@twin_config, +); +like(join('', @twin_config), qr/^host twinnode-xcat-ib \{$/m, + 'the writer produced the InfiniBand twin declaration the query must step over'); +my ($tname, $tip, $thw) = + xCAT_plugin::dhcp::_query_isc_static_host('twinnode', @twin_config); +is($thw, 'hardware-address = b8:3f:d2:4a:68:aa', + 'the twin declaration does not replace the primary hardware address'); +is($tip, 'ip-address = 192.0.2.21', + 'the twin declaration does not replace the primary address'); + +# `makedhcp -q` runs with no configuration in memory, so the query reads dhcpd.conf. A +# file it cannot read must not look like a node without a reservation. +my $tmpdir = tempdir(CLEANUP => 1); +my $conffile = "$tmpdir/dhcpd.conf"; +open(my $wfh, '>', $conffile) or BAIL_OUT("cannot write $conffile: $!"); +print $wfh @dhcpconf; +close($wfh); +open(my $efh, '>', "$tmpdir/empty.conf") or BAIL_OUT("cannot write empty.conf: $!"); +close($efh); + +{ + no warnings 'once'; + $xCAT_plugin::dhcp::dhcpconffile = $conffile; +} +my ($fname, $fip, $fhw, $ferr) = + xCAT_plugin::dhcp::_query_isc_static_host('xcat30-cn'); +is($ferr, undef, 'a readable dhcpd.conf reports no error'); +is($fip, 'ip-address = 192.168.201.30', + 'the query reads the reservation from dhcpd.conf'); + +{ + no warnings 'once'; + $xCAT_plugin::dhcp::dhcpconffile = "$tmpdir/absent.conf"; +} +my ($aname, $aip, $ahw, $aerr) = + xCAT_plugin::dhcp::_query_isc_static_host('xcat30-cn'); +like($aerr, qr/\Qabsent.conf\E/, + 'a dhcpd.conf the query cannot read is reported as an error'); +is($aip, undef, 'a dhcpd.conf the query cannot read reports no address'); + +{ + no warnings 'once'; + $xCAT_plugin::dhcp::dhcpconffile = "$tmpdir/empty.conf"; +} +my ($ename, $eip, $ehw, $eerr) = + xCAT_plugin::dhcp::_query_isc_static_host('xcat30-cn'); +is($eerr, undef, 'an empty dhcpd.conf is not an error'); +is($eip, undef, 'an empty dhcpd.conf reports no reservation'); + +# listnode is what `makedhcp -q` calls. On an ISC-limited release it must pass the read +# failure to the caller instead of answering "no reservation found". +{ + no warnings 'once'; + $xCAT_plugin::dhcp::distro = 'ubuntu22.04'; + $xCAT_plugin::dhcp::dhcpconffile = "$tmpdir/absent.conf"; +} +my @responses; +eval { + local $SIG{ALRM} = sub { die "listnode did not return\n" }; + alarm 20; + xCAT_plugin::dhcp::listnode('xcat30-cn', sub { push @responses, $_[0] }); + alarm 0; + 1; +}; +alarm 0; +is(scalar(@responses), 1, 'a query answers once when dhcpd.conf cannot be read'); +like($responses[0]->{error}->[0], qr/\Qabsent.conf\E/, + 'the query reports the unreadable dhcpd.conf to the caller'); +is($responses[0]->{errorcode}->[0], 1, 'the query fails when dhcpd.conf cannot be read'); + done_testing(); diff --git a/xCAT-test/unit/dhcp_static_host_markers.t b/xCAT-test/unit/dhcp_static_host_markers.t index 8409a1965..9e9fb43e1 100644 --- a/xCAT-test/unit/dhcp_static_host_markers.t +++ b/xCAT-test/unit/dhcp_static_host_markers.t @@ -181,4 +181,22 @@ like($lazy_replacement, qr/^host node06-current \{$/m, is($update_started, 1, 'the successful replacement records that cleanup has started'); +# A node can hold several declarations. A deletion that names one hostname must remove +# that declaration only. +my @hostname_config; +xCAT_plugin::dhcp::_add_isc_static_host( + 'node07', 'node07', '00:11:22:33:44:33', 1, + 'eth0', '192.0.2.9', '', 0, \@hostname_config, +); +xCAT_plugin::dhcp::_add_isc_static_host( + 'node07', 'node07-ib', '00:11:22:33:44:44', 32, + 'ib0', '192.0.2.9', '', 1, \@hostname_config, +); +xCAT_plugin::dhcp::_delete_isc_static_host('node07', \@hostname_config, 'node07-ib'); +my $hostname_remaining = join( '', @hostname_config ); +unlike($hostname_remaining, qr/^host node07-ib \{$/m, + 'a deletion that names a hostname removes that declaration'); +like($hostname_remaining, qr/^host node07 \{$/m, + 'a deletion that names a hostname keeps the other declarations of the node'); + done_testing();