diff --git a/mdb/models.py b/mdb/models.py index f8d53f0..da52c25 100644 --- a/mdb/models.py +++ b/mdb/models.py @@ -6,6 +6,15 @@ from django.utils.translation import gettext_lazy as _ from mdb.validators import validate_hostname, validate_macaddr +# The boot file of a PXE installable host. UEFI x86-64 clients, PXE architecture 7 or 9 +# in their vendor class, get the UEFI loader instead. +PXE_FILENAME = "pxelinux.0" +PXE_EFI_FILENAME = "grubx64.efi" +PXE_EFI_CLIENT = " or ".join( + f'substring(option vendor-class-identifier, 0, 20) = "PXEClient:Arch:{arch}"' + for arch in ("00007", "00009") +) + class Nameserver(models.Model): hostname = models.CharField(max_length=256) @@ -250,7 +259,15 @@ class DhcpConfig(models.Model): content += ( f"\tfixed-address {_if.host.hostname}.{_if.domain.domain_name};\n" ) - if len(_if.pxe_filename) > 0: + if _if.pxe_filename == PXE_FILENAME: + content += ( + f"\tif {PXE_EFI_CLIENT} {{\n" + f'\t\tfilename "{PXE_EFI_FILENAME}";\n' + "\t} else {\n" + f'\t\tfilename "{PXE_FILENAME}";\n' + "\t}\n" + ) + elif _if.pxe_filename: content += f'\tfilename "{_if.pxe_filename}";\n' content += "}\n" diff --git a/mdb/signals.py b/mdb/signals.py index 8314a73..f01a04c 100644 --- a/mdb/signals.py +++ b/mdb/signals.py @@ -5,7 +5,7 @@ import uuid from django.db.models.signals import post_save, pre_delete, pre_save from django.dispatch import receiver -from mdb.models import Host, Interface, Ip4Address, Ip4Subnet, Ip6Subnet +from mdb.models import PXE_FILENAME, Host, Interface, Ip4Address, Ip4Subnet, Ip6Subnet from mdb.utils import format_domain_serial_and_add_one logger = logging.getLogger(__name__) @@ -87,10 +87,6 @@ def update_domain_serial_when_change_to_host(sender, instance, created, **kwargs subnet.save() -# The netboot host serves one menu to every machine DHCP sends to pxelinux. -PXE_FILENAME = "pxelinux.0" - - @receiver(post_save, sender=Host) def set_pxe_boot_when_host_changes(sender, instance, created, **kwargs): """An installable host netboots into the menu; unticking it stops that. diff --git a/tests/test_generated_configs.py b/tests/test_generated_configs.py index ef1d8f2..fa5c823 100644 --- a/tests/test_generated_configs.py +++ b/tests/test_generated_configs.py @@ -26,7 +26,12 @@ subnet 10.0.3.0 netmask 255.255.255.0 { host tut { \thardware ethernet f0:de:f1:58:be:1a; \tfixed-address tut.edb.technology; -\tfilename "pxelinux.0"; +\tif substring(option vendor-class-identifier, 0, 20) = "PXEClient:Arch:00007" \ +or substring(option vendor-class-identifier, 0, 20) = "PXEClient:Arch:00009" { +\t\tfilename "grubx64.efi"; +\t} else { +\t\tfilename "pxelinux.0"; +\t} } """ @@ -99,6 +104,13 @@ def test_dhcpd_configuration_without_pxe_filename(fixtures): assert "filename" not in conf +def test_dhcpd_configuration_with_custom_pxe_filename(fixtures): + Interface.objects.update(pxe_filename="custom.efi") + conf = DhcpConfig.objects.get().dhcpd_configuration() + assert '\tfilename "custom.efi";\n' in conf + assert "vendor-class-identifier" not in conf + + def test_dhcpd_configuration_without_dynamic_range(fixtures): Ip4Subnet.objects.update(dhcp_dynamic=False) assert "range" not in DhcpConfig.objects.get().dhcpd_configuration()