From 93b87ba0633290e612734975085c3eff3ba4e00a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Thu, 8 Oct 2026 16:25:39 -0300 Subject: [PATCH] test(http): characterize port defaults at boot URL callers --- xCAT-test/bats/dhcp_http_port.bats | 27 ++++++++ xCAT-test/bats/fixtures/dhcp-http.pl | 78 +++++++++++++++++++++++ xCAT-test/unit/dhcp_kea_plugin_intent.t | 28 ++++++++ xCAT-test/unit/http_boot_urls.t | 50 +++++++++++++++ xCAT-test/unit/mknb_xcatd_address.t | 22 +++++++ xCAT-test/unit/template_httpport_suffix.t | 18 +++--- xCAT-test/unit/xnba_semicolon.t | 13 ++++ 7 files changed, 228 insertions(+), 8 deletions(-) create mode 100644 xCAT-test/bats/dhcp_http_port.bats create mode 100644 xCAT-test/bats/fixtures/dhcp-http.pl create mode 100644 xCAT-test/unit/http_boot_urls.t diff --git a/xCAT-test/bats/dhcp_http_port.bats b/xCAT-test/bats/dhcp_http_port.bats new file mode 100644 index 000000000..60ddfbb27 --- /dev/null +++ b/xCAT-test/bats/dhcp_http_port.bats @@ -0,0 +1,27 @@ +#!/usr/bin/env bats + +setup() +{ + [ "$(uname -s)" = Linux ] || skip 'DHCP rendering requires Linux namespaces' +} + +@test "ISC subnet and node callers preserve their HTTP port defaults" { + [ -n "$BATS_TEST_TMPDIR" ] || return 1 + for port in null '""' 0 '"0"' '"80"' '"080"' '"8080"'; do + fixture=$(mktemp -d "$BATS_TEST_TMPDIR/isc.XXXXXX") + mkdir -p "$fixture/etc/sysconfig" "$fixture/tftp" "$fixture/config" + printf 'DHCPDARGS=""\n' > "$fixture/etc/sysconfig/dhcpd" + run bwrap --die-with-parent --unshare-net --ro-bind / / --dev /dev --proc /proc \ + --tmpfs /tmp --ro-bind "$BATS_TEST_DIRNAME/../.." /tmp/source \ + --bind "$fixture" /tmp/fixture --bind "$fixture/etc" /etc --chdir /tmp/fixture \ + perl /tmp/source/xCAT-test/bats/fixtures/dhcp-http.pl "$port" + [ "$status" -eq 0 ] || { printf '%s\n' "$output"; return 1; } + case "$port" in + '"080"') suffix=:080 ;; + '"8080"') suffix=:8080 ;; + *) suffix= ;; + esac + grep -Fq "option cumulus-provision-url \"http://192.0.2.1$suffix/install/postscripts/cumulusztp\";" "$fixture/dhcpd.conf" + grep -Fq "http://192.0.2.1$suffix/tftpboot/petitboot/cn1" "$fixture/omshell" + done +} diff --git a/xCAT-test/bats/fixtures/dhcp-http.pl b/xCAT-test/bats/fixtures/dhcp-http.pl new file mode 100644 index 000000000..c77f856d8 --- /dev/null +++ b/xCAT-test/bats/fixtures/dhcp-http.pl @@ -0,0 +1,78 @@ +use strict; +use warnings; +## no critic (Modules::RequireFilenameMatchesPackage) +use FindBin; +use lib "$FindBin::Bin/../../../perl-xCAT"; +use lib "$FindBin::Bin/../../../xCAT-server/lib/perl"; +use File::Path qw(make_path); +use JSON (); + +BEGIN { + *CORE::GLOBAL::readpipe = sub { return '' if $_[0] eq 'ip -6 route'; die "Unexpected command: $_[0]"; }; +} + +$ENV{XCATROOT} = "$FindBin::Bin/../../../xCAT-server"; +$ENV{XCATCFG} = '/tmp/fixture/config'; +make_path($ENV{XCATCFG}, '/tmp/xcat'); +require xCAT::Utils; +require xCAT::TableUtils; +no warnings qw(redefine once); +local *xCAT::TableUtils::getTftpDir = sub { return '/tmp/fixture/tftp'; }; +local *xCAT::Utils::osver = sub { return 'rhels9.4'; }; +my $plugin = "$FindBin::Bin/../../../xCAT-server/lib/xcat/plugins/dhcp.pm"; +require $plugin; + +my $port = JSON->new->allow_nonref->decode($ARGV[0]); +my %site = (httpport => $port, domain => 'example.invalid', dhcpinterfaces => 'eth0', dnshandler => 'none'); +local %::XCATSITEVALS = %site; +local $xCAT_plugin::dhcp::dhcpconffile = '/tmp/fixture/dhcpd.conf'; +local *xCAT::TableUtils::get_site_attribute = sub { return $site{$_[-1]}; }; +local *xCAT::DHCP::Backend::new_backend = sub { return bless {}, 'HTTPTestBackend'; }; +local *xCAT::DHCP::OmapiRunner::key_algorithm_error = sub { return; }; +local *xCAT::Utils::isServiceNode = sub { return 0; }; +local *xCAT::Utils::isFIPS = sub { return 0; }; +local *xCAT::Utils::checkservicestatus = sub { return 0; }; +local *xCAT::Utils::restartservice = sub { return 0; }; +local *xCAT::MsgUtils::trace = sub { return; }; +local *xCAT::NetworkUtils::determinehostname = sub { return 'mn'; }; +local *xCAT::NetworkUtils::my_ip_facing = sub { return (0, '192.0.2.1'); }; +local *xCAT::NetworkUtils::nodeonmynet = sub { return 1; }; +local *xCAT::NetworkUtils::getipaddr = sub { return '192.0.2.10'; }; +local *xCAT_plugin::dhcp::getipaddr = sub { return '192.0.2.10'; }; +local *xCAT_plugin::dhcp::local_ipv4_routes = sub { return ['192.0.2.0', 'eth0', '255.255.255.0', '']; }; +local *xCAT::DBobjUtils::getnodetype = sub { return { cn1 => 'osi' }; }; +local *xCAT::DBobjUtils::getNetwkInfo = sub { return (cn1 => {mgtifname => 'eth0'}); }; +local *xCAT::Table::new = sub { return bless {name => $_[1]}, 'HTTPTestTable'; }; +local *xCAT_plugin::dhcp::_open_omshell_writer = sub { + open(my $fh, '>', '/tmp/fixture/omshell') or die $!; + return $fh; +}; +local *xCAT_plugin::dhcp::_close_omshell_writer = sub { close($_[0]) or die $!; }; +my @errors; +xCAT_plugin::dhcp::process_request({_xcatpreprocessed => [1], node => ['cn1'], arg => []}, + sub { push @errors, @{$_[0]{error} || []}; }); +die join("\n", @errors) if @errors; + +package HTTPTestBackend; +sub name { return 'isc'; } + +package HTTPTestTable; +sub getNodeAttribs { return {ip => '192.0.2.10'} if $_[0]{name} eq 'hosts'; return; } +sub getNodesAttribs { + my %rows = ( + noderes => {netboot => 'petitboot', tftpserver => '192.0.2.1'}, + mac => {mac => '52:54:00:00:00:10'}, nodetype => {os => 'rhels9'}, + chain => {currstate => 'shell'}, + ); + return $rows{$_[0]{name}} ? {cn1 => [$rows{$_[0]{name}}]} : {}; +} +sub getAllAttribs { + return () unless $_[0]{name} eq 'networks'; + return {net => '192.0.2.0', mask => '255.255.255.0', mgtifname => 'eth0', + domain => 'example.invalid', nameservers => '192.0.2.1'}; +} +sub getAttribs { + return {username => 'xcat_key', password => 'dGVzdA=='} if $_[0]{name} eq 'passwd'; + return {domain => 'example.invalid', nameservers => '192.0.2.1', tftpserver => '192.0.2.1'}; +} +sub close { return; } diff --git a/xCAT-test/unit/dhcp_kea_plugin_intent.t b/xCAT-test/unit/dhcp_kea_plugin_intent.t index 319e64a0c..dc74929aa 100644 --- a/xCAT-test/unit/dhcp_kea_plugin_intent.t +++ b/xCAT-test/unit/dhcp_kea_plugin_intent.t @@ -1903,4 +1903,32 @@ foreach my $case (@invalid_mac_cases) { ); } +for my $case ([undef, ''], ['', ''], [0, ''], ['0', ''], ['80', ''], ['080', ':080'], ['8080', ':8080']) { + my ($port, $suffix) = @$case; + no warnings 'redefine'; + local *xCAT::TableUtils::get_site_attribute = sub { + return ($port) if $_[-1] eq 'httpport' && defined($port); + return; + }; + for my $method (qw(onie petitboot)) { + my $boot = xCAT_plugin::dhcp::kea_boot_for_node('cn1', {netboot => $method}, + {currstate => 'install'}, {}, {}, '192.0.2.1'); + my $path = $method eq 'onie' ? '/install/onie/onie-installer' : '/tftpboot/petitboot/cn1'; + is($boot->{'option-data'}[0]{data}, "http://192.0.2.1$suffix$path", + "$method reservation preserves site port defaulting"); + } +} +for my $case (['80', ''], ['080', ':080'], ['8080', ':8080']) { + my ($port, $suffix) = @$case; + my $opal = xCAT_plugin::dhcp::kea_opal_client_class('192.0.2.0', 24, '192.0.2.1', $port); + is($opal->{'option-data'}[0]{data}, "http://192.0.2.1$suffix/tftpboot/pxelinux.cfg/p/192.0.2.0_24", + 'OPAL preserves the supplied port'); + my $subnet = xCAT_plugin::dhcp::kea_subnet4_intent( + DHCPKeaIntentNetTable->new(\%network_entry), '10.0.0.0', '255.255.255.0', 'eth0', 0, 1, $port, + ); + my ($onie) = grep { $_->{name} eq 'xcat-onie-10.0.0.0_24' } @{$subnet->{client_classes}}; + is($onie->{'option-data'}[0]{data}, "http://10.0.0.1$suffix/install/onie/onie-installer", + 'subnet generation preserves the supplied port'); +} + done_testing(); diff --git a/xCAT-test/unit/http_boot_urls.t b/xCAT-test/unit/http_boot_urls.t new file mode 100644 index 000000000..224aa741e --- /dev/null +++ b/xCAT-test/unit/http_boot_urls.t @@ -0,0 +1,50 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use FindBin; +use lib "$FindBin::Bin/../../perl-xCAT"; +use File::Path qw(make_path); +use File::Slurper qw(write_text); +use File::Temp qw(tempdir); +use Test::More; +use xCAT::DHCP::BootPolicy; + +my $root = tempdir(CLEANUP => 1); +make_path("$root/boot/grub2"); +write_text("$root/boot/grub2/grub2.riscv64", 'loader'); +my %network = (net => '192.0.2.0', prefix => 24, next_server => '192.0.2.1'); + +for my $case ([undef, ''], ['', ''], [0, ''], ['0', ''], ['80', ''], ['080', ':080'], ['8080', ':8080']) { + my ($port, $suffix) = @$case; + my $base = "http://192.0.2.1$suffix"; + my $http = xCAT::DHCP::BootPolicy->kea_httpboot_network_classes( + %network, httpport => $port, tftpdir => $root, + ); + is_deeply([map { $_->{'boot-file-name'} } @$http], ["$base/tftpboot/boot/grub2/grub2.riscv64"], + 'HTTP boot preserves the site port default'); + + my $onie = xCAT::DHCP::BootPolicy->kea_onie_network_classes(%network, httpport => $port); + is($onie->[0]{'option-data'}[0]{data}, "$base/install/onie/onie-installer", + 'ONIE preserves the site port default'); + + my $net = xCAT::DHCP::BootPolicy->kea_xnba_network_classes( + %network, httpport => $port, ipxe_bios => 1, ipxe_uefi => 1, + ); + is_deeply([map { $_->{'boot-file-name'} } @$net], [ + "$base/tftpboot/xcat/ipxe/nets/192.0.2.0_24", + "$base/tftpboot/xcat/ipxe/nets/192.0.2.0_24.uefi", + ], 'BIOS and UEFI discovery URLs preserve the site port default'); + + my $node = xCAT::DHCP::BootPolicy->kea_xnba_node_classes(nodes => [{ + node => 'cn1', mac => '52:54:00:00:00:01', netboot => 'ipxe', + next_server => '192.0.2.1', httpport => $port, + }]); + is_deeply([map { $_->{'boot-file-name'} } @$node], [ + "$base/tftpboot/xcat/ipxe/nodes/cn1", + "$base/tftpboot/xcat/ipxe/nodes/cn1.uefi", + 'xcat/ipxe/i386/undionly.kpxe', + 'xcat/ipxe/x86_64-sb/snponly-shim.efi', + ], 'per-node BIOS and UEFI URLs preserve the site port default'); +} + +done_testing(); diff --git a/xCAT-test/unit/mknb_xcatd_address.t b/xCAT-test/unit/mknb_xcatd_address.t index a2ed5747f..5cbf80c6f 100644 --- a/xCAT-test/unit/mknb_xcatd_address.t +++ b/xCAT-test/unit/mknb_xcatd_address.t @@ -698,4 +698,26 @@ ok(!-e $s390x_qemu_path, 'a :noboot network gets no QEMU s390x configuration'); %xCAT::TableUtils::site_extra = (); $xCAT::NetworkUtils::nic_ips = undef; +%xCAT::TableUtils::site_extra = (); +use_reporter_address_maps(); +for my $case ([undef, ''], ['', ''], [0, ''], ['0', ''], ['80', ''], ['080', ':080'], ['8080', ':8080']) { + my ($port, $suffix) = @$case; + $xCAT::TableUtils::site_httpport = $port; + prepare_tftpdir($tmpdir, 'http-port', 'x86_64'); + generation_succeeded(run_mknb('x86_64'), 'mknb renders the HTTP-port case'); + for my $loader (qw(xnba ipxe)) { + for my $mode ('', '.uefi') { + my $script = read_config("$xCAT::TableUtils::tftpdir/xcat/$loader/nets/192.168.144.0_20$mode"); + my @authorities = $script =~ m{^imgfetch -n \w+ (http://[^/\s]+)}mg; + is_deeply(\@authorities, [('http://${next-server}' . $suffix) x 2], + "$loader$mode preserves HTTP port defaulting for kernel and initrd"); + } + } + prepare_tftpdir($tmpdir, 'http-port-grub', 'riscv64'); + generation_succeeded(run_mknb('riscv64'), 'mknb renders the GRUB HTTP-port case'); + like(read_config("$xCAT::TableUtils::tftpdir/boot/grub2/grub.cfg-C0A89"), + qr/^ set root=\Qhttp,192.168.148.10$suffix\E$/m, + 'GRUB discovery preserves HTTP port defaulting'); +} + done_testing(); diff --git a/xCAT-test/unit/template_httpport_suffix.t b/xCAT-test/unit/template_httpport_suffix.t index 0a709937e..aaa704e20 100644 --- a/xCAT-test/unit/template_httpport_suffix.t +++ b/xCAT-test/unit/template_httpport_suffix.t @@ -13,20 +13,15 @@ use Test::More; use XCAT::Test::File qw(repo_path); my $module = repo_path('xCAT-server/lib/perl/xCAT/Template.pm'); -plan skip_all => 'Template.pm not found' unless -r $module; my @incs = ( repo_path('perl-xCAT'), repo_path('xCAT-server/lib/perl'), ); -# A mismatched DBI aborts the process instead of dying, so ask a child before -# loading the module in this process. -my $devnull = File::Spec->devnull(); -my $probe = join( ' ', $^X, ( map { "-I$_" } @incs ), - '-e', "'require xCAT::Template; 1'", ">$devnull", "2>&1" ); -plan skip_all => 'xCAT::Template cannot be loaded here' if system($probe) != 0; - +my $config = File::Temp->newdir(); +$ENV{XCATROOT} = repo_path('xCAT-server'); +$ENV{XCATCFG} = "$config"; require lib; lib->import(@incs); require xCAT::Template; @@ -130,4 +125,11 @@ local *xCAT::TableUtils::get_site_Master = sub { return '192.0.2.10'; }; 'a local mirror keeps a custom port' ); } +for my $case ([undef, ''], ['', ''], [0, ':0'], ['0', ':0'], ['80', ''], ['080', ':080'], ['8080', ':8080']) { + my ($port, $suffix) = @$case; + is(suffix($port), $suffix, 'Template keeps its defined-value defaulting'); + like($render->($port), qr{^url --url http://192\.0\.2\.10\Q$suffix\E/install/pkg$}m, + 'rendered installer URL keeps the same port'); +} + done_testing(); diff --git a/xCAT-test/unit/xnba_semicolon.t b/xCAT-test/unit/xnba_semicolon.t index 51e5ad29e..fcbc39b95 100644 --- a/xCAT-test/unit/xnba_semicolon.t +++ b/xCAT-test/unit/xnba_semicolon.t @@ -126,6 +126,19 @@ write_text("$root/tftp/xcat/pxelinux.0", 'loader'); is_deeply(nodeset('present', 'install', {kernel => 'xcat/memdisk', initrd => 'initrd', kcmdline => 'quiet'}), [], 'an installed pxelinux needs no warning'); +for my $case ([undef, ''], ['', ''], [0, ''], ['0', ''], ['80', ''], ['080', ':080'], ['8080', ':8080']) { + my ($value, $suffix) = @$case; + $port = $value; + nodeset('port', 'install', {kernel => 'xcat/ubuntu24.04/vmlinuz', initrd => 'xcat/initrd', kcmdline => 'quiet'}); + for my $name ('port', 'port.uefi') { + my @urls = script($name) =~ /^imgfetch -n \w+ (\S+)$/mg; + is_deeply(\@urls, [ + 'http://${next-server}' . $suffix . '/tftpboot/xcat/ubuntu24.04/vmlinuz', + 'http://${next-server}' . $suffix . '/tftpboot/xcat/initrd', + ], "$name retains its HTTP port defaulting"); + } +} + done_testing(); package Local::BootTable;