2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-05-05 16:49:08 +00:00

Render Kea additional classes by version

This commit is contained in:
Vinícius Ferrão
2026-04-23 19:39:25 -03:00
parent 714c0785b6
commit e0e04f017d
4 changed files with 146 additions and 24 deletions

View File

@@ -62,10 +62,10 @@ sub render_dhcp4_config {
$dhcp4{'control-socket'} = $intent->{'control-socket'} if $intent->{'control-socket'};
$dhcp4{'hooks-libraries'} = $intent->{'hooks-libraries'} if $intent->{'hooks-libraries'};
$dhcp4{'option-def'} = $intent->{'option-def'} if $intent->{'option-def'};
$dhcp4{'client-classes'} = $intent->{'client-classes'} if $intent->{'client-classes'};
$dhcp4{'option-data'} = $intent->{'option-data'} if $intent->{'option-data'};
$dhcp4{'dhcp-ddns'} = $intent->{'dhcp-ddns'} if $intent->{'dhcp-ddns'};
$dhcp4{'option-def'} = $intent->{'option-def'} if $intent->{'option-def'};
$dhcp4{'client-classes'} = [ map { $self->_render_client_class($_) } @{ $intent->{'client-classes'} } ] if $intent->{'client-classes'};
$dhcp4{'option-data'} = $intent->{'option-data'} if $intent->{'option-data'};
$dhcp4{'dhcp-ddns'} = $intent->{'dhcp-ddns'} if $intent->{'dhcp-ddns'};
foreach my $field (qw/ddns-send-updates ddns-override-no-update ddns-override-client-update ddns-qualifying-suffix ddns-update-on-renew/) {
$dhcp4{$field} = $intent->{$field} if exists $intent->{$field};
}
@@ -659,13 +659,19 @@ sub _render_subnet4 {
$rendered{interface} = $subnet->{interface} if defined $subnet->{interface};
$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};
$rendered{'option-data'} = _first_defined( $subnet->{'option-data'}, $subnet->{option_data} );
my $additional_classes = _first_defined(
$subnet->{additional_client_classes},
$subnet->{'evaluate-additional-classes'},
$subnet->{evaluate_additional_classes},
$subnet->{'require-client-classes'},
$subnet->{require_client_classes},
);
$rendered{ $self->_additional_class_list_field() } = $additional_classes if defined $additional_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};
@@ -678,6 +684,26 @@ sub _render_subnet4 {
return \%rendered;
}
sub _render_client_class {
my ( $self, $client_class ) = @_;
my %rendered = %$client_class;
my $additional_only = _first_defined(
$client_class->{additional_only},
$client_class->{'only-in-additional-list'},
$client_class->{only_in_additional_list},
$client_class->{'only-if-required'},
$client_class->{only_if_required},
);
delete @rendered{qw/additional_only only_in_additional_list only_if_required/};
delete $rendered{'only-in-additional-list'};
delete $rendered{'only-if-required'};
$rendered{ $self->_additional_class_flag_field() } = $additional_only if defined $additional_only;
return \%rendered;
}
sub _render_subnet6 {
my ( $self, $subnet ) = @_;
@@ -761,6 +787,51 @@ sub _control_agent_not_found {
return 0;
}
sub _additional_class_flag_field {
my ($self) = @_;
return $self->_use_modern_additional_class_syntax() ? 'only-in-additional-list' : 'only-if-required';
}
sub _additional_class_list_field {
my ($self) = @_;
return $self->_use_modern_additional_class_syntax() ? 'evaluate-additional-classes' : 'require-client-classes';
}
sub _use_modern_additional_class_syntax {
my ($self) = @_;
return 1 if $self->{additional_class_syntax} && $self->{additional_class_syntax} eq 'modern';
return 0 if $self->{additional_class_syntax} && $self->{additional_class_syntax} eq 'legacy';
my $version = $self->kea_version();
return _version_at_least( $version, '2.7.4' );
}
sub kea_version {
my ($self) = @_;
return $self->{kea_version} if defined $self->{kea_version};
return $self->{_detected_kea_version} if defined $self->{_detected_kea_version};
my $command = $self->{kea_dhcp4_command} || _command_path('kea-dhcp4');
return unless $command;
my $output = '';
if ( open( my $version_fh, '-|', $command, '-V' ) ) {
local $/;
$output = <$version_fh> || '';
close($version_fh);
}
if ( $output =~ /(\d+(?:\.\d+){1,2})/ ) {
$self->{_detected_kea_version} = $1;
}
return $self->{_detected_kea_version};
}
sub _first_defined {
my @values = @_;
foreach my $value (@values) {
@@ -770,6 +841,25 @@ sub _first_defined {
return;
}
sub _version_at_least {
my ( $version, $minimum ) = @_;
return 0 unless defined($version) && $version =~ /^\d+(?:\.\d+)*/;
my @version_parts = split /\./, $version;
my @minimum_parts = split /\./, $minimum;
my $max = @version_parts > @minimum_parts ? @version_parts : @minimum_parts;
for my $idx ( 0 .. $max - 1 ) {
my $left = $version_parts[$idx] || 0;
my $right = $minimum_parts[$idx] || 0;
return 1 if $left > $right;
return 0 if $left < $right;
}
return 1;
}
sub _integer {
my ($value) = @_;

View File

@@ -2777,7 +2777,7 @@ sub kea_subnet4_intent
);
$subnet{interface} = $interface unless $remote;
if ($opal_class) {
$subnet{'require-client-classes'} = [ $opal_class->{name} ];
$subnet{additional_client_classes} = [ $opal_class->{name} ];
$subnet{client_classes} = [$opal_class];
}
@@ -2794,10 +2794,10 @@ sub kea_opal_client_class
$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 => $class_name,
test => 'option[93].hex == 0x000e',
additional_only => JSON::true,
'option-data' => [
{
name => 'conf-file',
data => "http://$tftp:$httpport/tftpboot/pxelinux.cfg/p/" . $net . "_" . $prefix,

View File

@@ -33,10 +33,10 @@ my $json = $backend->render_dhcp4_config(
'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 => 'xcat-opal-v3-192.168.122.0-24',
test => 'option[93].hex == 0x000e',
additional_only => JSON::true,
'option-data' => [
{ name => 'conf-file', data => 'http://192.168.122.1:80/tftpboot/pxelinux.cfg/p/192.168.122.0_24' },
],
},
@@ -64,7 +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'],
additional_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' },

View File

@@ -10,7 +10,7 @@ use Test::More;
use xCAT::DHCP::Backend::Kea;
my $backend = xCAT::DHCP::Backend::Kea->new();
my $backend = xCAT::DHCP::Backend::Kea->new( kea_version => '2.4.1' );
my $json = $backend->render_dhcp4_config(
{
interfaces => ['eth0'],
@@ -22,7 +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'],
additional_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' },
@@ -44,10 +44,10 @@ my $json = $backend->render_dhcp4_config(
'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 => 'xcat-opal-v3-10.0.0.0-24',
test => 'option[93].hex == 0x000e',
additional_only => JSON::true,
'option-data' => [
{ name => 'conf-file', data => 'http://10.0.0.1/tftpboot/pxelinux.cfg/p/10.0.0.0_24' },
],
},
@@ -123,6 +123,38 @@ is_deeply(
'client classes are preserved, including subnet-specific OPAL conf-file class'
);
my $modern_backend = xCAT::DHCP::Backend::Kea->new( kea_version => '3.0.1' );
my $modern_json = $modern_backend->render_dhcp4_config(
{
subnets => [
{
id => 3,
subnet => '10.0.2.0/24',
pools => [],
additional_client_classes => ['xcat-opal-v3-10.0.2.0-24'],
},
],
'client-classes' => [
{
name => 'xcat-opal-v3-10.0.2.0-24',
test => 'option[93].hex == 0x000e',
additional_only => JSON::true,
},
],
}
);
my $modern_config = decode_json($modern_json);
my $modern_subnet = $modern_config->{Dhcp4}{subnet4}[0];
my $modern_class = $modern_config->{Dhcp4}{'client-classes'}[0];
is_deeply(
$modern_subnet->{'evaluate-additional-classes'},
['xcat-opal-v3-10.0.2.0-24'],
'Kea 3.x renders modern subnet additional-class evaluation field'
);
ok( !exists $modern_subnet->{'require-client-classes'}, 'Kea 3.x output omits deprecated subnet additional-class field' );
is( $modern_class->{'only-in-additional-list'}, JSON::true, 'Kea 3.x renders modern class additional-evaluation flag' );
ok( !exists $modern_class->{'only-if-required'}, 'Kea 3.x output omits deprecated class additional-evaluation flag' );
my $empty_boot_json = $backend->render_dhcp4_config(
{
subnets => [