diff --git a/xCAT-test/unit/dhcp_boot_policy.t b/xCAT-test/unit/dhcp_boot_policy.t index d7021bc21..f73f78d91 100644 --- a/xCAT-test/unit/dhcp_boot_policy.t +++ b/xCAT-test/unit/dhcp_boot_policy.t @@ -145,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/srv/tftpboot/boot/grub2/grub2.riscv64', + 'the HTTP boot URL follows the configured HTTP port and TFTP root' +); + +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 index d65e33a94..d8a22aac2 100644 --- a/xCAT-test/unit/dhcp_isc_client_arch.t +++ b/xCAT-test/unit/dhcp_isc_client_arch.t @@ -42,7 +42,24 @@ SKIP: { cmp_ok( $pos{riscv64}, '<', $pos{yaboot}, 'riscv64 is rendered before the /yaboot fallback, so it is reachable' ); } +# HTTP boot firmware (architecture id 28) needs the boot file as a URL and only +# accepts an offer that is tagged HTTPClient +ok( + $source =~ /client-architecture = 00:1c \{[^\n]*\n\s*push \@netent, "\s*option vendor-class-identifier \\"HTTPClient\\";[^\n]*\n\s*push \@netent, "\s*filename \\"http:\/\/\$tftp\$portsuffix\$tftpdir\/boot\/grub2\/grub2\.riscv64\\";/, + 'the ISC subnet block hands RISC-V HTTP boot clients the boot loader as a URL', +); +my $httpboot_pos = $-[0]; +SKIP: { + skip 'the riscv64 branches were not both found', 2 unless defined $pos{riscv64} && defined $httpboot_pos; + cmp_ok( $pos{riscv64}, '<', $httpboot_pos, 'the PXE branch keeps its place before the HTTP boot branch' ); + cmp_ok( $httpboot_pos, '<', $pos{yaboot}, 'the HTTP boot branch is rendered before the /yaboot fallback' ); +} + my @riscv_ids = $source =~ /client-architecture = (00:1[9a-e])/g; -is_deeply( \@riscv_ids, ['00:1b'], 'only the RISC-V 64-bit UEFI architecture id (27) is mapped' ); +is_deeply( + [ sort @riscv_ids ], + [ '00:1b', '00:1c' ], + 'only the RISC-V 64-bit UEFI architecture ids (27 and 28) are mapped', +); 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' );