mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-10-12 08:12:33 +00:00
refactor(dhcp): build the x86 boot statements in BootPolicy
dhcp.pm wrote the ISC host statements of netboot=xnba and netboot=pxe nodes inline. Each Kea class builder named the xNBA files and the xNBA user-class test on its own. Move the host statements into isc_node_boot_statements. Give every ISC and Kea builder its loader files and second-stage tests from x86_loader. The rendered statements, lines and classes do not change.
This commit is contained in:
@@ -6,14 +6,14 @@ use warnings;
|
||||
sub kea_client_classes {
|
||||
my ( $class, %opts ) = @_;
|
||||
|
||||
my $xnba_user_class = xnba_user_class_test();
|
||||
my $loader = $class->x86_loader();
|
||||
my $uefi_x64_arch_match = uefi_x64_client_architecture_match_expr();
|
||||
my $etherboot = etherboot_vendor_class_test();
|
||||
# No substitute when the loader is not on disk: naming a file the TFTP
|
||||
# server does not have costs the client a timeout it cannot diagnose, and a
|
||||
# different loader boots something nobody asked for.
|
||||
my $bios_boot = $opts{xnba_kpxe} ? 'xcat/xnba.kpxe' : '';
|
||||
my $uefi_boot = $opts{xnba_efi} ? 'xcat/xnba.efi' : '';
|
||||
my $bios_boot = $opts{xnba_kpxe} ? $loader->{bios} : '';
|
||||
my $uefi_boot = $opts{xnba_efi} ? $loader->{uefi} : '';
|
||||
my @classes;
|
||||
|
||||
push @classes, @{ $opts{xnba_node_classes} || [] };
|
||||
@@ -26,7 +26,7 @@ sub kea_client_classes {
|
||||
push @classes, (
|
||||
{
|
||||
name => 'xcat-bios',
|
||||
test => "option[93].hex == 0x0000 and not ($xnba_user_class)",
|
||||
test => "option[93].hex == 0x0000 and not ($loader->{kea_second_stage_bios})",
|
||||
'boot-file-name' => $bios_boot,
|
||||
},
|
||||
# Etherboot predates option 93: it says what it is in option 60 and
|
||||
@@ -42,7 +42,7 @@ sub kea_client_classes {
|
||||
if ($uefi_boot ne '') {
|
||||
push @classes, {
|
||||
name => 'xcat-uefi-x64',
|
||||
test => "($uefi_x64_arch_match) and not ($xnba_user_class)",
|
||||
test => "($uefi_x64_arch_match) and not ($loader->{kea_second_stage_uefi})",
|
||||
'boot-file-name' => $uefi_boot,
|
||||
};
|
||||
}
|
||||
@@ -288,7 +288,7 @@ sub isc_client_architecture_lines {
|
||||
my $portsuffix = $opts{portsuffix} // '';
|
||||
my $net = $opts{net} // '';
|
||||
my $maskbits = $opts{prefix} // '';
|
||||
my $xnba = $class->isc_xnba_user_class_test();
|
||||
my $loader = $class->x86_loader();
|
||||
|
||||
# Which loaders are actually on disk. A branch naming a file the TFTP server
|
||||
# does not have costs the client a full timeout it cannot diagnose, so the
|
||||
@@ -297,8 +297,8 @@ sub isc_client_architecture_lines {
|
||||
my $present = $opts{loader_present} || sub { return 1 };
|
||||
my $tftpdir = $opts{tftpdir} || '/tftpboot';
|
||||
$tftpdir =~ s{/+$}{};
|
||||
my $kpxe = $present->("$tftpdir/xcat/xnba.kpxe");
|
||||
my $efi = $present->("$tftpdir/xcat/xnba.efi");
|
||||
my $kpxe = $present->("$tftpdir/$loader->{bios}");
|
||||
my $efi = $present->("$tftpdir/$loader->{uefi}");
|
||||
|
||||
# Each entry is the head of one branch and its statements. They are chained
|
||||
# afterwards so dropping one still leaves a well-formed if/else chain.
|
||||
@@ -306,39 +306,39 @@ sub isc_client_architecture_lines {
|
||||
|
||||
if ($kpxe) {
|
||||
push @branches, [
|
||||
"$xnba and option client-architecture = 00:00 { #x86, xCAT Network Boot Agent\n",
|
||||
"$loader->{isc_second_stage_bios} and option client-architecture = 00:00 { #x86, xCAT Network Boot Agent\n",
|
||||
" always-broadcast on;\n",
|
||||
" filename = \"http://$tftp$portsuffix/tftpboot/xcat/xnba/nets/${net}_${maskbits}\";\n",
|
||||
];
|
||||
}
|
||||
if ($efi) {
|
||||
push @branches, [
|
||||
"$xnba and option client-architecture = 00:09 { #x86, xCAT Network Boot Agent\n",
|
||||
"$loader->{isc_second_stage_uefi} and option client-architecture = 00:09 { #x86, xCAT Network Boot Agent\n",
|
||||
" filename = \"http://$tftp$portsuffix/tftpboot/xcat/xnba/nets/${net}_${maskbits}.uefi\";\n",
|
||||
],
|
||||
[
|
||||
"$xnba and option client-architecture = 00:07 { #x86-64 UEFI, xCAT Network Boot Agent\n",
|
||||
"$loader->{isc_second_stage_uefi} and option client-architecture = 00:07 { #x86-64 UEFI, xCAT Network Boot Agent\n",
|
||||
" filename = \"http://$tftp$portsuffix/tftpboot/xcat/xnba/nets/${net}_${maskbits}.uefi\";\n",
|
||||
];
|
||||
}
|
||||
if ($kpxe) {
|
||||
push @branches, [
|
||||
"option client-architecture = 00:00 { #x86\n",
|
||||
" filename \"xcat/xnba.kpxe\";\n",
|
||||
" filename \"$loader->{bios}\";\n",
|
||||
],
|
||||
[
|
||||
"option vendor-class-identifier = \"Etherboot-5.4\" { #x86\n",
|
||||
" filename \"xcat/xnba.kpxe\";\n",
|
||||
" filename \"$loader->{bios}\";\n",
|
||||
];
|
||||
}
|
||||
if ($efi) {
|
||||
push @branches, [
|
||||
"option client-architecture = 00:07 { #x86_64 uefi\n ",
|
||||
" filename \"xcat/xnba.efi\";\n",
|
||||
" filename \"$loader->{uefi}\";\n",
|
||||
],
|
||||
[
|
||||
"option client-architecture = 00:09 { #x86_64 uefi alternative id\n ",
|
||||
" filename \"xcat/xnba.efi\";\n",
|
||||
" filename \"$loader->{uefi}\";\n",
|
||||
],
|
||||
|
||||
# 0x0010 is the same x86-64 UEFI firmware and loader as 0x0007,
|
||||
@@ -346,7 +346,7 @@ sub isc_client_architecture_lines {
|
||||
# a mainstream client falls through to /yaboot.
|
||||
[
|
||||
"option client-architecture = 00:10 { #x86_64 uefi http boot\n ",
|
||||
" filename \"xcat/xnba.efi\";\n",
|
||||
" filename \"$loader->{uefi}\";\n",
|
||||
];
|
||||
}
|
||||
|
||||
@@ -426,7 +426,7 @@ sub kea_xnba_node_classes {
|
||||
my ( $class, %opts ) = @_;
|
||||
|
||||
my $nodes = $opts{nodes} || [];
|
||||
my $xnba_user_class = xnba_user_class_test();
|
||||
my $loader = $class->x86_loader();
|
||||
my $uefi_x64_arch_match = uefi_x64_client_architecture_match_expr();
|
||||
my @classes;
|
||||
|
||||
@@ -440,7 +440,7 @@ sub kea_xnba_node_classes {
|
||||
|
||||
push @classes, {
|
||||
name => "$class_base-bios",
|
||||
test => "$xnba_user_class and option[93].hex == 0x0000 and $mac_test",
|
||||
test => "$loader->{kea_second_stage_bios} and option[93].hex == 0x0000 and $mac_test",
|
||||
'boot-file-name' => $base_url,
|
||||
'user-context' => _xnba_user_context($node),
|
||||
};
|
||||
@@ -448,7 +448,7 @@ sub kea_xnba_node_classes {
|
||||
if ( $opts{xnba_efi} ) {
|
||||
push @classes, {
|
||||
name => "$class_base-uefi",
|
||||
test => "$xnba_user_class and ($uefi_x64_arch_match) and $mac_test",
|
||||
test => "$loader->{kea_second_stage_uefi} and ($uefi_x64_arch_match) and $mac_test",
|
||||
'boot-file-name' => "$base_url.uefi",
|
||||
'user-context' => _xnba_user_context($node),
|
||||
};
|
||||
@@ -666,7 +666,7 @@ sub kea_xnba_network_classes {
|
||||
|
||||
return [] unless $opts{net} && defined $opts{prefix} && $opts{next_server};
|
||||
|
||||
my $xnba_user_class = xnba_user_class_test();
|
||||
my $loader = $class->x86_loader();
|
||||
my $uefi_x64_arch_match = uefi_x64_client_architecture_match_expr();
|
||||
my $httpport = $opts{httpport} || '80';
|
||||
my $portsuffix = ( $httpport eq '80' ) ? '' : ":$httpport";
|
||||
@@ -680,7 +680,7 @@ sub kea_xnba_network_classes {
|
||||
if ( $opts{xnba_kpxe} ) {
|
||||
push @classes, {
|
||||
name => "xcat-xnba-net-$safe_network-bios",
|
||||
test => "$xnba_user_class and option[93].hex == 0x0000",
|
||||
test => "$loader->{kea_second_stage_bios} and option[93].hex == 0x0000",
|
||||
'boot-file-name' => $base_url,
|
||||
additional_only => 1,
|
||||
};
|
||||
@@ -689,7 +689,7 @@ sub kea_xnba_network_classes {
|
||||
if ( $opts{xnba_efi} ) {
|
||||
push @classes, {
|
||||
name => "xcat-xnba-net-$safe_network-uefi",
|
||||
test => "$xnba_user_class and ($uefi_x64_arch_match)",
|
||||
test => "$loader->{kea_second_stage_uefi} and ($uefi_x64_arch_match)",
|
||||
'boot-file-name' => "$base_url.uefi",
|
||||
additional_only => 1,
|
||||
};
|
||||
@@ -698,6 +698,72 @@ sub kea_xnba_network_classes {
|
||||
return \@classes;
|
||||
}
|
||||
|
||||
# The x86 loader: its BIOS and UEFI files, and the tests that recognize a client that runs it and
|
||||
# can fetch the boot script.
|
||||
sub x86_loader {
|
||||
my ($class) = @_;
|
||||
|
||||
return {
|
||||
bios => 'xcat/xnba.kpxe',
|
||||
uefi => 'xcat/xnba.efi',
|
||||
isc_second_stage_bios => $class->isc_xnba_user_class_test(),
|
||||
isc_second_stage_uefi => $class->isc_xnba_user_class_test(),
|
||||
isc_san_boot => 'exists gpxe.bus-id',
|
||||
kea_second_stage_bios => xnba_user_class_test(),
|
||||
kea_second_stage_uefi => xnba_user_class_test(),
|
||||
};
|
||||
}
|
||||
|
||||
# The ISC host statements that choose the boot file of a node with netboot xnba or pxe. They reach
|
||||
# dhcpd through omshell, so their quotes are escaped.
|
||||
sub isc_node_boot_statements {
|
||||
my ( $class, %opts ) = @_;
|
||||
|
||||
my $netboot = $opts{netboot} // '';
|
||||
return '' if $netboot ne 'xnba' && $netboot ne 'pxe';
|
||||
return '' unless $opts{loader_present};
|
||||
|
||||
my $loader = $class->x86_loader();
|
||||
my $bios = $loader->{bios};
|
||||
my $uefi = $loader->{uefi};
|
||||
my $bios_next = _omapi_quote( $loader->{isc_second_stage_bios} );
|
||||
my $uefi_next = _omapi_quote( $loader->{isc_second_stage_uefi} );
|
||||
my $san_boot = _omapi_quote( $loader->{isc_san_boot} );
|
||||
my $currstate = $opts{currstate} // '';
|
||||
my $script = 'http://' . ( $opts{next_server} // '' ) . ( $opts{portsuffix} // '' )
|
||||
. '/tftpboot/xcat/xnba/nodes/' . ( $opts{node} // '' );
|
||||
|
||||
# A node in state boot or iscsiboot has an operating system and is left to start it (S-31).
|
||||
# An iSCSI root still needs the loader, which attaches the disk.
|
||||
if ( $currstate eq 'boot' || $currstate eq 'iscsiboot' ) {
|
||||
return 'filename = \"\";' unless $opts{iscsi};
|
||||
return 'if ' . $san_boot . ' { filename = \"\"; } else if exists client-architecture { filename = \"' . $bios . '\"; } '
|
||||
if $netboot eq 'pxe';
|
||||
return 'if option client-architecture = 00:00 and not ' . $san_boot . ' { filename = \"' . $bios . '\"; } else { filename = \"\"; } ';
|
||||
}
|
||||
return 'if option vendor-class-identifier = \"ScaleMP\" { filename = \"vsmp/pxelinux.0\"; } else { filename = \"pxelinux.0\"; }'
|
||||
if $netboot eq 'pxe';
|
||||
|
||||
my $uefi_mode = $opts{uefi} // 0;
|
||||
if ( ( $uefi_mode == 2 && $currstate =~ /^install/ ) || $currstate =~ /^winshell/ ) {
|
||||
return 'if option client-architecture = 00:00 or option client-architecture = 00:07 or option client-architecture = 00:09 { filename = \"\"; option vendor-class-identifier \"PXEClient\"; } else { filename = \"\"; }'
|
||||
if $opts{proxydhcp} && $opts{proxydhcp}->();
|
||||
return 'if ' . $bios_next . ' and option client-architecture = 00:00 { always-broadcast on; filename = \"' . $script . '\"; } else if option client-architecture = 00:07 or option client-architecture = 00:09 { filename = \"\"; option vendor-class-identifier \"PXEClient\"; } else if option client-architecture = 00:00 { filename = \"' . $bios . '\"; } else { filename = \"\"; }';
|
||||
}
|
||||
if ($uefi_mode) {
|
||||
# dhcpd's expression grammar has no grouping, so each UEFI architecture gets its own branch.
|
||||
my $uefi_script = 'filename = \"' . $script . '.uefi\";';
|
||||
return 'if ' . $bios_next . ' and option client-architecture = 00:00 { always-broadcast on; filename = \"' . $script . '\"; } else if ' . $uefi_next . ' and option client-architecture = 00:09 { ' . $uefi_script . ' } else if ' . $uefi_next . ' and option client-architecture = 00:07 { ' . $uefi_script . ' } else if option client-architecture = 00:07 { filename = \"' . $uefi . '\"; } else if option client-architecture = 00:00 { filename = \"' . $bios . '\"; } else { filename = \"\"; }';
|
||||
}
|
||||
return 'if ' . $bios_next . ' and option client-architecture = 00:00 { filename = \"' . $script . '\"; } else if option client-architecture = 00:00 { filename = \"' . $bios . '\"; } else { filename = \"\"; }';
|
||||
}
|
||||
|
||||
sub _omapi_quote {
|
||||
my ($text) = @_;
|
||||
$text =~ s/"/\\"/g;
|
||||
return $text;
|
||||
}
|
||||
|
||||
sub xnba_user_class_test {
|
||||
return "(option[77].exists and (option[77].text == 'xNBA' or option[77].hex == 0x784e4241 or substring(option[77].hex,1,4) == 'xNBA'))";
|
||||
}
|
||||
|
||||
@@ -1123,60 +1123,19 @@ sub addnode
|
||||
}
|
||||
my $douefi = check_uefi_support($ntent);
|
||||
|
||||
# These statements reach dhcpd through omshell, so the quoting is \"
|
||||
# rather than ". Both encodings of option 77 are accepted for the same
|
||||
# reason as in the per-network classes: a second stage that
|
||||
# length-prefixes its user class per RFC 3004 would chainload forever.
|
||||
my $xnba_user_class = xCAT::DHCP::BootPolicy->isc_xnba_user_class_test(quote => '\"');
|
||||
|
||||
if ($nrent and $nrent->{netboot} and $nrent->{netboot} eq 'xnba' and $lstatements !~ /filename/) {
|
||||
if (-f "$tftpdir/xcat/xnba.kpxe") {
|
||||
if ($chainent and $chainent->{currstate} and ($chainent->{currstate} eq 'iscsiboot' or $chainent->{currstate} eq 'boot')) {
|
||||
|
||||
# A node in state boot or iscsiboot has an operating system
|
||||
# and must be left to start it -- spec.md S-31. iSCSI still
|
||||
# needs a loader, because the root disk is on the network
|
||||
# and gPXE attaches it: BIOS firmware is given xnba.kpxe,
|
||||
# and the second stage, which announces gpxe.bus-id, is
|
||||
# given nothing.
|
||||
if ($doiscsi) {
|
||||
$lstatements = 'if option client-architecture = 00:00 and not exists gpxe.bus-id { filename = \"xcat/xnba.kpxe\"; } else { filename = \"\"; } ' . $lstatements;
|
||||
} else {
|
||||
$lstatements = 'filename = \"\";' . $lstatements;
|
||||
}
|
||||
} else {
|
||||
|
||||
# If proxydhcp daemon is enabled for windows deployment, do vendor-class-identifier of "PXEClient" to bump it over to proxydhcp.c
|
||||
if (($douefi == 2 and $chainent->{currstate} =~ /^install/) or $chainent->{currstate} =~ /^winshell/) {
|
||||
if (proxydhcp($nrent)) { #proxy dhcp required in uefi invocation
|
||||
$lstatements = 'if option client-architecture = 00:00 or option client-architecture = 00:07 or option client-architecture = 00:09 { filename = \"\"; option vendor-class-identifier \"PXEClient\"; } else { filename = \"\"; }' . $lstatements; #If proxydhcp daemon is enable, use it.
|
||||
} else {
|
||||
$lstatements = 'if ' . $xnba_user_class . ' and option client-architecture = 00:00 { always-broadcast on; filename = \"http://' . $nxtsrv . $portsuffix . '/tftpboot/xcat/xnba/nodes/' . $node . '\"; } else if option client-architecture = 00:07 or option client-architecture = 00:09 { filename = \"\"; option vendor-class-identifier \"PXEClient\"; } else if option client-architecture = 00:00 { filename = \"xcat/xnba.kpxe\"; } else { filename = \"\"; }' . $lstatements; #Only PXE compliant clients should ever receive xNBA
|
||||
}
|
||||
} elsif ($douefi and $chainent->{currstate} ne "boot" and $chainent->{currstate} ne "iscsiboot") {
|
||||
# Separate branches rather than one parenthesised
|
||||
# alternation: dhcpd's expression grammar has no
|
||||
# grouping, so `... and (a or b) {` is a parse error.
|
||||
my $uefi_second_stage = 'filename = \"http://' . $nxtsrv . $portsuffix . '/tftpboot/xcat/xnba/nodes/' . $node . '.uefi\";';
|
||||
$lstatements = 'if ' . $xnba_user_class . ' and option client-architecture = 00:00 { always-broadcast on; filename = \"http://' . $nxtsrv . $portsuffix . '/tftpboot/xcat/xnba/nodes/' . $node . '\"; } else if ' . $xnba_user_class . ' and option client-architecture = 00:09 { ' . $uefi_second_stage . ' } else if ' . $xnba_user_class . ' and option client-architecture = 00:07 { ' . $uefi_second_stage . ' } else if option client-architecture = 00:07 { filename = \"xcat/xnba.efi\"; } else if option client-architecture = 00:00 { filename = \"xcat/xnba.kpxe\"; } else { filename = \"\"; }' . $lstatements; #Only PXE compliant clients should ever receive xNBA
|
||||
} else {
|
||||
$lstatements = 'if ' . $xnba_user_class . ' and option client-architecture = 00:00 { filename = \"http://' . $nxtsrv . $portsuffix . '/tftpboot/xcat/xnba/nodes/' . $node . '\"; } else if option client-architecture = 00:00 { filename = \"xcat/xnba.kpxe\"; } else { filename = \"\"; }' . $lstatements; #Only PXE compliant clients should ever receive xNBA
|
||||
}
|
||||
}
|
||||
} #TODO: warn when windows
|
||||
} elsif ($nrent and $nrent->{netboot} and $nrent->{netboot} eq 'pxe' and $lstatements !~ /filename/) {
|
||||
if (-f "$tftpdir/xcat/xnba.kpxe") {
|
||||
if ($chainent and $chainent->{currstate} and ($chainent->{currstate} eq 'iscsiboot' or $chainent->{currstate} eq 'boot')) {
|
||||
|
||||
# S-31 again, and the same $doiscsi gate.
|
||||
if ($doiscsi) {
|
||||
$lstatements = 'if exists gpxe.bus-id { filename = \"\"; } else if exists client-architecture { filename = \"xcat/xnba.kpxe\"; } ' . $lstatements;
|
||||
} else {
|
||||
$lstatements = 'filename = \"\";' . $lstatements;
|
||||
}
|
||||
} else {
|
||||
$lstatements = 'if option vendor-class-identifier = \"ScaleMP\" { filename = \"vsmp/pxelinux.0\"; } else { filename = \"pxelinux.0\"; }' . $lstatements;
|
||||
}
|
||||
if ($nrent and $nrent->{netboot} and ($nrent->{netboot} eq 'xnba' or $nrent->{netboot} eq 'pxe')) {
|
||||
if ($lstatements !~ /filename/) {
|
||||
$lstatements = xCAT::DHCP::BootPolicy->isc_node_boot_statements(
|
||||
netboot => $nrent->{netboot},
|
||||
loader_present => -f "$tftpdir/xcat/xnba.kpxe",
|
||||
iscsi => $doiscsi,
|
||||
currstate => $chainent ? $chainent->{currstate} : undef,
|
||||
uefi => $douefi,
|
||||
proxydhcp => sub { proxydhcp($nrent) },
|
||||
node => $node,
|
||||
next_server => $nxtsrv,
|
||||
portsuffix => $portsuffix,
|
||||
) . $lstatements;
|
||||
}
|
||||
} elsif ($nrent and $nrent->{netboot} and $nrent->{netboot} eq 'yaboot') {
|
||||
$lstatements = 'filename = \"/yb/node/yaboot-' . $node . '\";' . $lstatements;
|
||||
|
||||
Reference in New Issue
Block a user