From 714c0785b68fcbdd7792a81b8fca816556a0980b 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, 23 Apr 2026 19:19:29 -0300 Subject: [PATCH] Preserve OPAL conf-file handling for Kea --- perl-xCAT/xCAT/DHCP/Backend/Kea.pm | 2 + perl-xCAT/xCAT/DHCP/BootPolicy.pm | 2 +- xCAT-server/lib/xcat/plugins/dhcp.pm | 31 ++++++++++++- xCAT-server/share/xcat/tools/dhcpop | 51 ++++++++++----------- xCAT-test/unit/dhcp_boot_policy.t | 4 +- xCAT-test/unit/dhcp_kea_config_validation.t | 9 ++++ xCAT-test/unit/dhcp_kea_renderer.t | 24 +++++++++- 7 files changed, 91 insertions(+), 32 deletions(-) diff --git a/perl-xCAT/xCAT/DHCP/Backend/Kea.pm b/perl-xCAT/xCAT/DHCP/Backend/Kea.pm index 8b1e7c328..ddcda8bca 100644 --- a/perl-xCAT/xCAT/DHCP/Backend/Kea.pm +++ b/perl-xCAT/xCAT/DHCP/Backend/Kea.pm @@ -660,10 +660,12 @@ sub _render_subnet4 { $rendered{'next-server'} = _first_defined( $subnet->{'next-server'}, $subnet->{next_server} ); $rendered{'boot-file-name'} = _first_defined( $subnet->{'boot-file-name'}, $subnet->{boot_file_name} ); $rendered{'option-data'} = _first_defined( $subnet->{'option-data'}, $subnet->{option_data} ); + $rendered{'require-client-classes'} = _first_defined( $subnet->{'require-client-classes'}, $subnet->{require_client_classes} ); $rendered{reservations} = $subnet->{reservations} if $subnet->{reservations}; delete $rendered{'next-server'} unless defined $rendered{'next-server'}; delete $rendered{'boot-file-name'} unless defined $rendered{'boot-file-name'}; delete $rendered{'option-data'} unless defined $rendered{'option-data'}; + delete $rendered{'require-client-classes'} unless defined $rendered{'require-client-classes'}; if ( $subnet->{pools} ) { $rendered{pools} = $subnet->{pools}; diff --git a/perl-xCAT/xCAT/DHCP/BootPolicy.pm b/perl-xCAT/xCAT/DHCP/BootPolicy.pm index 102f006d1..1d918e5b3 100644 --- a/perl-xCAT/xCAT/DHCP/BootPolicy.pm +++ b/perl-xCAT/xCAT/DHCP/BootPolicy.pm @@ -37,7 +37,7 @@ sub kea_client_classes { }, { name => 'xcat-ppc64', - test => '(option[93].hex == 0x000c or option[93].hex == 0x000e)', + test => 'option[93].hex == 0x000c', 'boot-file-name' => '/boot/grub2/grub2.ppc', }, { diff --git a/xCAT-server/lib/xcat/plugins/dhcp.pm b/xCAT-server/lib/xcat/plugins/dhcp.pm index 35f1bd89e..31bb6b3f0 100644 --- a/xCAT-server/lib/xcat/plugins/dhcp.pm +++ b/xCAT-server/lib/xcat/plugins/dhcp.pm @@ -2388,6 +2388,7 @@ sub kea_build_dhcp4_intent my @routes = kea_ipv4_routes(@vnets); my @subnets; + my @opal_classes; my $id = 1; foreach my $route (@routes) { my ( $net, $netif, $mask, $flags ) = @$route; @@ -2406,6 +2407,7 @@ sub kea_build_dhcp4_intent my $subnet = kea_subnet4_intent($nettab, $net, $mask, $interface, $remote, $id, $httpport); return $subnet if $subnet->{error}; + push @opal_classes, @{ delete $subnet->{client_classes} || [] }; push @subnets, $subnet; $id++; } @@ -2416,7 +2418,7 @@ sub kea_build_dhcp4_intent valid_lifetime => kea_dhcp_lease_time(), 'option-def' => kea_option_defs(), 'option-data' => kea_global_option_data(), - 'client-classes' => kea_boot_client_classes(), + 'client-classes' => [ @{ kea_boot_client_classes() }, @opal_classes ], subnets => \@subnets, }; @@ -2765,6 +2767,7 @@ sub kea_subnet4_intent push @option_data, { name => 'domain-search', data => $domainstring } if $domainstring; my $prefix = kea_mask_to_prefix($mask); + my $opal_class = kea_opal_client_class($net, $prefix, $tftp, $httpport); my %subnet = ( id => $id, subnet => "$net/$prefix", @@ -2773,10 +2776,36 @@ sub kea_subnet4_intent next_server => $tftp, ); $subnet{interface} = $interface unless $remote; + if ($opal_class) { + $subnet{'require-client-classes'} = [ $opal_class->{name} ]; + $subnet{client_classes} = [$opal_class]; + } return \%subnet; } +sub kea_opal_client_class +{ + my ( $net, $prefix, $tftp, $httpport ) = @_; + + return unless $net && defined($prefix) && $tftp; + + my $class_name = "xcat-opal-v3-$net-$prefix"; + $class_name =~ s/[^A-Za-z0-9_.-]/_/g; + + return { + name => $class_name, + test => 'option[93].hex == 0x000e', + 'only-if-required' => JSON::true, + 'option-data' => [ + { + name => 'conf-file', + data => "http://$tftp:$httpport/tftpboot/pxelinux.cfg/p/" . $net . "_" . $prefix, + }, + ], + }; +} + sub kea_expand_request_nodes { my ( $req, $opt ) = @_; diff --git a/xCAT-server/share/xcat/tools/dhcpop b/xCAT-server/share/xcat/tools/dhcpop index a9b55cb03..1af7cf3fa 100755 --- a/xCAT-server/share/xcat/tools/dhcpop +++ b/xCAT-server/share/xcat/tools/dhcpop @@ -19,6 +19,8 @@ sub usage{ print " dhcphelper -r|--rm -m|--mac [ -a|--ip ] [ -n|--name ]\n"; print " delete the dhcp lease of specified , and \n"; print "\n"; + + return; } my $help; @@ -46,7 +48,7 @@ if($help){ } mkdir "/tmp/xcat" unless -d "/tmp/xcat"; - open(my $dhcplockfd, ">", "/tmp/xcat/dhcplock") or die "Unable to lock DHCP state: $!"; + open(my $dhcplockfd, ">", "/tmp/xcat/dhcplock") or die "Unable to lock DHCP state: $!"; ## no critic (InputOutput::RequireBriefOpen) flock($dhcplockfd, LOCK_EX); my $config4 = $backend->load_dhcp4_config(); @@ -103,41 +105,38 @@ if($help){ exit 1; } - my $omshell; - open($omshell, '|-', '/bin/sh', '-c', '/usr/bin/omshell >/dev/null') - or die "Unable to start omshell: $!"; - print $omshell "key " - . $id . " \"" - . $passwd . "\"\n"; - print $omshell "connect\n"; + my $omshell_commands = "key " . $id . " \"" . $passwd . "\"\n"; + $omshell_commands .= "connect\n"; if($hostname){ - print $omshell "new host\n"; - print $omshell - "set name = \"$hostname\"\n"; #Find and destroy conflict name - print $omshell "open\n"; - print $omshell "remove\n"; - print $omshell "close\n"; + $omshell_commands .= "new host\n"; + $omshell_commands .= "set name = \"$hostname\"\n"; #Find and destroy conflict name + $omshell_commands .= "open\n"; + $omshell_commands .= "remove\n"; + $omshell_commands .= "close\n"; } if ($mac) { - print $omshell "new host\n"; - print $omshell "set hardware-address = " . $mac - . "\n"; #find and destroy mac conflict - print $omshell "open\n"; - print $omshell "remove\n"; - print $omshell "close\n"; + $omshell_commands .= "new host\n"; + $omshell_commands .= "set hardware-address = " . $mac . "\n"; #find and destroy mac conflict + $omshell_commands .= "open\n"; + $omshell_commands .= "remove\n"; + $omshell_commands .= "close\n"; } if($ip){ - print $omshell "new host\n"; - print $omshell - "set ip-address = $ip\n"; #find and destroy ip conflict - print $omshell "open\n"; - print $omshell "remove\n"; - print $omshell "close\n"; + $omshell_commands .= "new host\n"; + $omshell_commands .= "set ip-address = $ip\n"; #find and destroy ip conflict + $omshell_commands .= "open\n"; + $omshell_commands .= "remove\n"; + $omshell_commands .= "close\n"; } + + my $omshell; + open($omshell, '|-', '/bin/sh', '-c', '/usr/bin/omshell >/dev/null') + or die "Unable to start omshell: $!"; + print $omshell $omshell_commands; close($omshell); }else{ &usage; diff --git a/xCAT-test/unit/dhcp_boot_policy.t b/xCAT-test/unit/dhcp_boot_policy.t index a21aa6cb3..6446c4bc2 100644 --- a/xCAT-test/unit/dhcp_boot_policy.t +++ b/xCAT-test/unit/dhcp_boot_policy.t @@ -13,7 +13,6 @@ is( scalar @$fallback_classes, 4, 'Kea boot policy omits xNBA classes when xNBA my %fallback_by_name = map { $_->{name} => $_ } @$fallback_classes; is( $fallback_by_name{'xcat-bios'}{'boot-file-name'}, 'pxelinux.0', 'BIOS clients fall back to pxelinux.0 without xNBA loaders' ); ok( !exists $fallback_by_name{'xcat-xnba-bios'}, 'xNBA user-class is not advertised without xNBA kpxe' ); -like( $fallback_by_name{'xcat-ppc64'}{test}, qr/0x000e/, 'POWER class covers OPAL-v3 client architecture' ); my $classes = xCAT::DHCP::BootPolicy->kea_client_classes(xnba_kpxe => 1, xnba_efi => 1); is( scalar @$classes, 5, 'Kea boot policy renders expected xNBA client classes' ); @@ -26,8 +25,7 @@ like( $by_name{'xcat-uefi-x64'}{test}, qr/0x0009/, 'UEFI x64 class matches archi like( $by_name{'xcat-uefi-x64'}{test}, qr/not \(\(option\[77\]\.exists/, 'generic UEFI class excludes xNBA second-stage clients' ); is( $by_name{'xcat-aarch64'}{'boot-file-name'}, 'boot/grub2/grub2.aarch64', 'AArch64 clients receive grub2 boot file' ); is( $by_name{'xcat-ppc64'}{'boot-file-name'}, '/boot/grub2/grub2.ppc', 'POWER clients receive grub2 Open Firmware boot file' ); -like( $by_name{'xcat-ppc64'}{test}, qr/0x000c/, 'POWER class matches existing POWER architecture id' ); -like( $by_name{'xcat-ppc64'}{test}, qr/0x000e/, 'POWER class matches OPAL-v3 architecture id' ); +is( $by_name{'xcat-ppc64'}{test}, 'option[93].hex == 0x000c', 'POWER class keeps existing POWER architecture id' ); my $xnba_classes = xCAT::DHCP::BootPolicy->kea_xnba_node_classes( xnba_efi => 1, diff --git a/xCAT-test/unit/dhcp_kea_config_validation.t b/xCAT-test/unit/dhcp_kea_config_validation.t index 6c4dca530..a06fb64be 100644 --- a/xCAT-test/unit/dhcp_kea_config_validation.t +++ b/xCAT-test/unit/dhcp_kea_config_validation.t @@ -32,6 +32,14 @@ my $json = $backend->render_dhcp4_config( test => "(option[77].exists and (option[77].text == 'xNBA' or option[77].hex == 0x784e4241 or substring(option[77].hex,1,4) == 'xNBA')) and option[93].hex == 0x0000 and pkt4.mac == 0x525400123456", 'boot-file-name' => 'http://192.168.122.1:80/tftpboot/xcat/xnba/nodes/node01', }, + { + name => 'xcat-opal-v3-192.168.122.0-24', + test => 'option[93].hex == 0x000e', + 'only-if-required' => JSON::true, + 'option-data' => [ + { name => 'conf-file', data => 'http://192.168.122.1:80/tftpboot/pxelinux.cfg/p/192.168.122.0_24' }, + ], + }, { name => 'xcat-uefi-x64', test => "(option[93].hex == 0x0007 or option[93].hex == 0x0009) and not ((option[77].exists and (option[77].text == 'xNBA' or option[77].hex == 0x784e4241 or substring(option[77].hex,1,4) == 'xNBA')))", @@ -56,6 +64,7 @@ my $json = $backend->render_dhcp4_config( subnet => '192.168.122.0/24', dynamicrange => '192.168.122.100-192.168.122.120', next_server => '192.168.122.1', + 'require-client-classes' => ['xcat-opal-v3-192.168.122.0-24'], option_data => [ { name => 'routers', data => '192.168.122.1' }, { name => 'domain-name', data => 'cluster.test' }, diff --git a/xCAT-test/unit/dhcp_kea_renderer.t b/xCAT-test/unit/dhcp_kea_renderer.t index 60755f4e1..247ecde11 100644 --- a/xCAT-test/unit/dhcp_kea_renderer.t +++ b/xCAT-test/unit/dhcp_kea_renderer.t @@ -22,6 +22,7 @@ my $json = $backend->render_dhcp4_config( interface => 'eth0', dynamicrange => '10.0.0.100-10.0.0.120;10.0.0.130,10.0.0.140', next_server => '10.0.0.1', + 'require-client-classes' => ['xcat-opal-v3-10.0.0.0-24'], option_data => [ { name => 'routers', data => '10.0.0.1' }, { name => 'domain-name-servers', data => '10.0.0.2, 10.0.0.3' }, @@ -42,6 +43,14 @@ my $json = $backend->render_dhcp4_config( test => 'option[93].hex == 0x0007', 'boot-file-name' => 'xcat/xnba.efi', }, + { + name => 'xcat-opal-v3-10.0.0.0-24', + test => 'option[93].hex == 0x000e', + 'only-if-required' => JSON::true, + 'option-data' => [ + { name => 'conf-file', data => 'http://10.0.0.1/tftpboot/pxelinux.cfg/p/10.0.0.0_24' }, + ], + }, ], } ); @@ -76,6 +85,11 @@ is_deeply( ], 'subnet option-data is preserved' ); +is_deeply( + $subnet->{'require-client-classes'}, + ['xcat-opal-v3-10.0.0.0-24'], + 'subnet requests second-pass OPAL class evaluation for subnet-specific conf-file' +); is_deeply( $subnet->{reservations}, @@ -97,8 +111,16 @@ is_deeply( test => 'option[93].hex == 0x0007', 'boot-file-name' => 'xcat/xnba.efi', }, + { + name => 'xcat-opal-v3-10.0.0.0-24', + test => 'option[93].hex == 0x000e', + 'only-if-required' => JSON::true, + 'option-data' => [ + { name => 'conf-file', data => 'http://10.0.0.1/tftpboot/pxelinux.cfg/p/10.0.0.0_24' }, + ], + }, ], - 'client classes are preserved' + 'client classes are preserved, including subnet-specific OPAL conf-file class' ); my $empty_boot_json = $backend->render_dhcp4_config(