From c1f5bed83330920c9b00fc1f1d4e59141c9976d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20L=C3=B6ser?= Date: Tue, 28 Feb 2023 11:52:37 +0100 Subject: [PATCH] Fixes #36834 - Add SecureBoot support for arbitrary operating systems to "Grub2 UEFI" PXE loaders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This feature consists of three patches, one for foreman, one for smart-proxy, and one for puppet-foreman_proxy. This patch adds support for individual Network Bootstrap Programs (NBP) in order to enable network based installations of SecureBoot enabled hosts for arbitrary operating systems. SecureBoot expects to follow a chain of trust from the initial boot of the host to the loading of Linux kernel modules. The very first shim that is loaded determines which distribution is allowed to be booted or kexec'ed until next reboot. Currently the "Grub2 UEFI SecureBoot" PXE loader uses NBPs provided by the vendor of the Foreman/Smart Proxy host system. All hosts receive and execute the same binary. On SecureBoot enabled hosts, this limits installations to operating systems by the vendor of the Foreman/ Smart Proxy host system. Providing shim and GRUB2 by the vendor of the operating system to be installed allows Foreman to install any operating system on SecureBoot enabled hosts over network. To achieve this, the host's DHCP filename option is set to a shim/GRUB2 binary in a host specific directory based on their MAC address. Corresponding shim and GRUB2 binaries are copied into that directory along with the generated GRUB2 configuration files. When provisioning a host, the Smart Proxy checks in a directory - the so called "bootloader universe" - if NBPs are present matching the operating system, operating system version, and architecture of the host to be installed. If this is the case, the NBPs are copied from the bootloader universe directory to the host specific directory. If not, the default NBPs provided by the vendor of the Foreman/Smart Proxy host system are used as fallback. The bootloader universe can be configured via `foreman-installer --foreman-proxy-tftp-bootloader-universe` and is unconfigured by default. Up to now, shim and GRUB2 binaries have to be retrieved and set up in the bootloader universe directory manually according to the documentation. An automatic way to provide OS dependent NBPs will be added in future. In case no bootloader universe directory is configured or there are no NBPs present matching the operating system, operating system version, and architecture of the host to be installed, the behaviour of the "Grub2 UEFI" PXE loaders does not change to the behavior prior to this feature. Implementation notes: --------------------- * To be future proof (e.g. to be able to provide NBPs in the bootloader universe for other PXE loaders without running into any filename conflicts) and for better structure, the PXE kind is prepended as a first directory level inside the bootloader universe. * The operating system version inside the bootloader universe consists of the major and minor version (if applicable) of the operating system separated by a dot (`.`). If no NBPs are configured for a specific operating system version the fallback directory `default` is used. * To simplify things on Foreman side in future, symlinks are created for the shim (boot-sb.efi) and GRUB2 (boot.efi) binaries. * Inside the TFTP root directory a new directory `host_config` is created for storing all the host specific directories. Full example: ------------- [root@vm ~]# hammer host info --id 241 | grep -E "(MAC address|Operating System)" MAC address: 00:50:56:b4:75:5e Operating System: AlmaLinux 8.9 [root@vm ~]# tree /usr/local/share/bootloader-universe/ /usr/local/share/bootloader_universe/ └── pxegrub2 └── almalinux ├── 8.9 │   └── x86_64 │   ├── boot.efi -> grubx64.efi │   ├── boot-sb.efi -> shimx64.efi │   ├── grubx64.efi │   └── shimx64.efi └── default └── x86_64 ├── boot.efi -> grubx64.efi ├── boot-sb.efi -> shimx64.efi ├── grubx64.efi └── shimx64.efi [root@vm ~]# hammer host update --id 241 --build true [root@vm ~]# tree /var/lib/tftpboot/host_config /var/lib/tftpboot/host_config └── 00-50-56-a3-41-a8 └── grub2 ├── boot.efi -> grubx64.efi ├── boot-sb.efi -> shimx64.efi ├── grub.cfg ├── grub.cfg-00:50:56:a3:41:a8 ├── grub.cfg-01-00-50-56-a3-41-a8 ├── grubx64.efi ├── os_info └── shimx64.efi [root@vm ~]# grep -B2 00-50-56-b4-75-5e /var/lib/dhcpd/dhcpd.leases hardware ethernet 00:50:56:b4:75:5e; fixed-address 192.168.145.84; supersede server.filename = "00-50-56-b4-75-5e/grub2/boot-sb.efi"; [root@vm ~]# pesign -S -i /var/lib/tftpboot/grub2/00-50-56-b4-75-5e/boot.efi | grep "Microsoft Windows UEFI Driver Publisher" The signer's common name is Microsoft Windows UEFI Driver Publisher --- app/models/concerns/orchestration/dhcp.rb | 16 +++++++- app/models/concerns/orchestration/tftp.rb | 10 ++++- app/models/concerns/pxe_loader_support.rb | 12 +++--- app/services/proxy_api/tftp.rb | 6 +++ test/factories/architecture.rb | 4 ++ test/factories/operatingsystem.rb | 11 ++++++ .../concerns/pxe_loader_support_test.rb | 29 ++++++++------ test/models/operatingsystem_test.rb | 4 +- test/models/orchestration/compute_test.rb | 4 +- test/models/orchestration/dhcp_test.rb | 20 +++++----- test/models/orchestration/tftp_test.rb | 38 +++++++++++++++---- 11 files changed, 114 insertions(+), 40 deletions(-) diff --git a/app/models/concerns/orchestration/dhcp.rb b/app/models/concerns/orchestration/dhcp.rb index a7899c1089c..fd2d1cade82 100644 --- a/app/models/concerns/orchestration/dhcp.rb +++ b/app/models/concerns/orchestration/dhcp.rb @@ -108,6 +108,20 @@ def build_dhcp_record(record_mac) end end + def dhcp_filename(record_mac) + filename = operatingsystem.boot_filename(host) + if filename.include? "@@subdir@@" + if host.subnet&.tftp&.has_capability?(:TFTP, :bootloader_universe) + filename = filename.gsub("@@subdir@@", "host_config/#{record_mac.tr(':', '-').downcase}") + filename = filename.gsub(/\/grub\w*\.efi$/, "/boot.efi") + return filename.gsub(/\/shim\w*\.efi$/, "/boot-sb.efi") + else + return filename.gsub("@@subdir@@/", "") + end + end + filename + end + # returns a hash of dhcp record settings def dhcp_attrs(record_mac) raise ::Foreman::Exception.new(N_("DHCP not supported for this NIC")) unless dhcp? @@ -124,7 +138,7 @@ def dhcp_attrs(record_mac) if provision? dhcp_attr[:nextServer] = boot_server unless host.pxe_loader == 'None' - filename = operatingsystem.boot_filename(host) + filename = dhcp_filename(record_mac) dhcp_attr[:filename] = filename if filename.present? if jumpstart? jumpstart_arguments = os.jumpstart_params host, model.vendor_class diff --git a/app/models/concerns/orchestration/tftp.rb b/app/models/concerns/orchestration/tftp.rb index 2665cab80f5..e1852038d78 100644 --- a/app/models/concerns/orchestration/tftp.rb +++ b/app/models/concerns/orchestration/tftp.rb @@ -87,7 +87,15 @@ def setTFTP(kind) logger.info "Deploying TFTP #{kind} configuration for #{host.name}" each_unique_feasible_tftp_proxy do |proxy| mac_addresses_for_provisioning.each do |mac_addr| - proxy.set(kind, mac_addr, :pxeconfig => content) + proxy.set(kind, mac_addr, { + :pxeconfig => content, + :targetos => host.operatingsystem.name.downcase, + :major => host.operatingsystem.major, + :minor => host.operatingsystem.minor, + :arch => host.arch.name, + :bootfilename_efi => host.arch.bootfilename_efi, + :build => host.build?, + }) end end else diff --git a/app/models/concerns/pxe_loader_support.rb b/app/models/concerns/pxe_loader_support.rb index c98632d7691..ff4c63b9b7c 100644 --- a/app/models/concerns/pxe_loader_support.rb +++ b/app/models/concerns/pxe_loader_support.rb @@ -5,7 +5,7 @@ module PxeLoaderSupport PXE_KINDS = { :PXELinux => /^(pxelinux.*|PXELinux (BIOS|UEFI))$/, :PXEGrub => /^(grub\/|Grub UEFI).*/, - :PXEGrub2 => /^(grub2\/|Grub2 (BIOS|UEFI|ELF)|http.*grub2\/).*/, + :PXEGrub2 => /(^Grub2 (BIOS|UEFI|ELF).*|\/?grub2\/)/, :iPXE => /^((iPXE|http.*\/ipxe-).*|ipxe\.efi|undionly\.kpxe)$/, }.with_indifferent_access.freeze @@ -26,11 +26,11 @@ def all_loaders_map(precision = 'x64', httpboot_host = "httpboot_host") "Grub UEFI" => "grub/grub#{precision}.efi", "Grub2 BIOS" => "grub2/grub#{precision}.0", "Grub2 ELF" => "grub2/grub#{precision}.elf", - "Grub2 UEFI" => "grub2/grub#{precision}.efi", - "Grub2 UEFI SecureBoot" => "grub2/shim#{precision}.efi", - "Grub2 UEFI HTTP" => "http://#{httpboot_host}/httpboot/grub2/grub#{precision}.efi", - "Grub2 UEFI HTTPS" => "https://#{httpboot_host}/httpboot/grub2/grub#{precision}.efi", - "Grub2 UEFI HTTPS SecureBoot" => "https://#{httpboot_host}/httpboot/grub2/shim#{precision}.efi", + "Grub2 UEFI" => "@@subdir@@/grub2/grub#{precision}.efi", + "Grub2 UEFI SecureBoot" => "@@subdir@@/grub2/shim#{precision}.efi", + "Grub2 UEFI HTTP" => "http://#{httpboot_host}/httpboot/@@subdir@@/grub2/grub#{precision}.efi", + "Grub2 UEFI HTTPS" => "https://#{httpboot_host}/httpboot/@@subdir@@/grub2/grub#{precision}.efi", + "Grub2 UEFI HTTPS SecureBoot" => "https://#{httpboot_host}/httpboot/@@subdir@@/grub2/shim#{precision}.efi", "iPXE Embedded" => nil, # renders directly as foreman_url('iPXE') "iPXE UEFI HTTP" => "http://#{httpboot_host}/httpboot/ipxe-#{precision}.efi", "iPXE Chain BIOS" => "undionly-ipxe.0", diff --git a/app/services/proxy_api/tftp.rb b/app/services/proxy_api/tftp.rb index dc4f0582ca7..23ac86ecb31 100644 --- a/app/services/proxy_api/tftp.rb +++ b/app/services/proxy_api/tftp.rb @@ -10,6 +10,12 @@ def initialize(args) # [+mac+] : MAC address # [+args+] : Hash containing # :pxeconfig => String containing the configuration + # :targetos => String containing the lowercase operating system name + # :major => String containing the operating system major version + # :minor => String containing the operating system minor version + # :arch => String containing the operating system architecture + # :bootfilename_efi => String containing the architecture specific boot filename suffix + # :build => Boolean stating if the host is in build mode # Returns : Boolean status def set(kind, mac, args) parse(post(args, "#{kind}/#{mac}")) diff --git a/test/factories/architecture.rb b/test/factories/architecture.rb index 24de8e3daa8..14b587f74cc 100644 --- a/test/factories/architecture.rb +++ b/test/factories/architecture.rb @@ -5,5 +5,9 @@ trait :for_snapshots_x86_64 do name { 'x86_64' } end + + trait :x64 do + name { 'x64' } + end end end diff --git a/test/factories/operatingsystem.rb b/test/factories/operatingsystem.rb index 4004448dc25..4b237b225c9 100644 --- a/test/factories/operatingsystem.rb +++ b/test/factories/operatingsystem.rb @@ -160,6 +160,17 @@ title { 'Red Hat Enterprise Linux 7.5' } end + factory :rhel9, class: Redhat do + name { 'RHEL' } + major { '9' } + minor { '0' } + type { 'Redhat' } + title { 'Red Hat Enterprise Linux 9.0' } + architectures { [FactoryBot.build(:architecture, :x64)] } + media { [FactoryBot.build(:rhel_for_snapshots)] } + ptables { [FactoryBot.build(:ptable, name: 'ptable')] } + end + factory :for_snapshots_centos_7_0, class: Redhat do name { 'CentOS' } major { '7' } diff --git a/test/models/concerns/pxe_loader_support_test.rb b/test/models/concerns/pxe_loader_support_test.rb index 9a431957234..b430750c940 100644 --- a/test/models/concerns/pxe_loader_support_test.rb +++ b/test/models/concerns/pxe_loader_support_test.rb @@ -32,18 +32,28 @@ def setup assert_equal :PXEGrub, @subject.pxe_loader_kind(@host) end - test "PXEGrub2 is found for given filename" do - @host.pxe_loader = "grub2/grubx64.efi" + test "PXEGrub2 is found for grubx64.elf filename" do + @host.pxe_loader = "grub2/grubx64.elf" + assert_equal :PXEGrub2, @subject.pxe_loader_kind(@host) + end + + test "PXEGrub2 is found for grubx64.0 filename" do + @host.pxe_loader = "grub2/grubx64.0" + assert_equal :PXEGrub2, @subject.pxe_loader_kind(@host) + end + + test "PXEGrub2 is found for grubx64.efi filename" do + @host.pxe_loader = "host_config/#{@host.mac.tr(':', '-')}/grub2/grubx64.efi" assert_equal :PXEGrub2, @subject.pxe_loader_kind(@host) end test "PXEGrub2 is found for shimx64.efi filename" do - @host.pxe_loader = "grub2/shimx64.efi" + @host.pxe_loader = "host_config/#{@host.mac.tr(':', '-')}/grub2/shimx64.efi" assert_equal :PXEGrub2, @subject.pxe_loader_kind(@host) end test "PXEGrub2 is found for shimia32.efi filename" do - @host.pxe_loader = "grub2/shimia32.efi" + @host.pxe_loader = "host_config/#{@host.mac.tr(':', '-')}/grub2/shimia32.efi" assert_equal :PXEGrub2, @subject.pxe_loader_kind(@host) end @@ -88,22 +98,17 @@ def setup end test "PXEGrub2 is found for http://smart_proxy/tftp/grub2/grubx64.efi filename" do - @host.pxe_loader = "http://smart_proxy/tftp/grub2/grubx64.efi" + @host.pxe_loader = "http://smart_proxy/tftp/host_config/#{@host.mac.tr(':', '-')}/grub2/grubx64.efi" assert_equal :PXEGrub2, @subject.pxe_loader_kind(@host) end test "PXEGrub2 is found for https://smart_proxy/tftp/grub2/grubx64.efi filename" do - @host.pxe_loader = "https://smart_proxy/tftp/grub2/grubx64.efi" - assert_equal :PXEGrub2, @subject.pxe_loader_kind(@host) - end - - test "PXEGrub2 is found for https://smart_proxy/tftp/grub2/shimx64.efi filename" do - @host.pxe_loader = "https://smart_proxy/tftp/grub2/shimx64.efi" + @host.pxe_loader = "https://smart_proxy/tftp/host_config/#{@host.mac.tr(':', '-')}/grub2/grubx64.efi" assert_equal :PXEGrub2, @subject.pxe_loader_kind(@host) end test "PXEGrub2 is found for https://smart_proxy/tftp/grub2/shimx64.efi filename" do - @host.pxe_loader = "https://smart_proxy/tftp/grub2/shimx64.efi" + @host.pxe_loader = "https://smart_proxy/tftp/host_config/#{@host.mac.tr(':', '-')}/grub2/shimx64.efi" assert_equal :PXEGrub2, @subject.pxe_loader_kind(@host) end diff --git a/test/models/operatingsystem_test.rb b/test/models/operatingsystem_test.rb index 825934bfede..63ca371ae77 100644 --- a/test/models/operatingsystem_test.rb +++ b/test/models/operatingsystem_test.rb @@ -379,13 +379,13 @@ class OperatingsystemTest < ActiveSupport::TestCase test 'should be the smart proxy and httpboot port for UEFI HTTP' do SmartProxy.any_instance.expects(:setting).with(:HTTPBoot, 'http_port').returns(1234) host = FactoryBot.build(:host, :managed, :with_tftp_and_httpboot_subnet, pxe_loader: 'Grub2 UEFI HTTP') - assert_match(%r{http://somewhere.*net:1234/httpboot/grub2/grubx64.efi}, host.operatingsystem.boot_filename(host)) + assert_match(%r{http://somewhere.*net:1234/httpboot/@@subdir@@/grub2/grubx64.efi}, host.operatingsystem.boot_filename(host)) end test 'should be the smart proxy and httpboot port for UEFI HTTPS' do SmartProxy.any_instance.expects(:setting).with(:HTTPBoot, 'https_port').returns(1235) host = FactoryBot.build(:host, :managed, :with_tftp_and_httpboot_subnet, pxe_loader: 'Grub2 UEFI HTTPS') - assert_match(%r{https://somewhere.*net:1235/httpboot/grub2/grubx64.efi}, host.operatingsystem.boot_filename(host)) + assert_match(%r{https://somewhere.*net:1235/httpboot/@@subdir@@/grub2/grubx64.efi}, host.operatingsystem.boot_filename(host)) end test 'should not raise an error without httpboot feature for PXE' do diff --git a/test/models/orchestration/compute_test.rb b/test/models/orchestration/compute_test.rb index 6428b944192..e13d549ba8c 100644 --- a/test/models/orchestration/compute_test.rb +++ b/test/models/orchestration/compute_test.rb @@ -96,14 +96,14 @@ class ComputeOrchestrationTest < ActiveSupport::TestCase test "if MAC is changed, dhcp_record cache is dropped" do cr = FactoryBot.build_stubbed(:libvirt_cr) cr.stubs(:provided_attributes).returns({:mac => :mac}) - host = FactoryBot.build_stubbed(:host, :managed, :compute_resource => cr) + host = FactoryBot.build_stubbed(:host, :managed, :compute_resource => cr, :pxe_loader => 'None') host.vm = mock("vm") fog_nic = OpenStruct.new(:mac => '00:00:00:00:01') host.vm.expects(:interfaces).returns([fog_nic]) host.vm.expects(:select_nic).returns(fog_nic) host.primary_interface.name = 'something' host.primary_interface.mac = '00:00:00:00:00:02' - host.primary_interface.subnet = FactoryBot.build_stubbed(:subnet, :dhcp, :network => '255.255.255.0') + host.primary_interface.subnet = FactoryBot.build_stubbed(:subnet_ipv4, :dhcp, :tftp, :network => '255.255.255.0') host.operatingsystem = FactoryBot.build_stubbed(:operatingsystem) refute_nil host.primary_interface.dhcp_records diff --git a/test/models/orchestration/dhcp_test.rb b/test/models/orchestration/dhcp_test.rb index ad4172223b8..2002058dcc3 100644 --- a/test/models/orchestration/dhcp_test.rb +++ b/test/models/orchestration/dhcp_test.rb @@ -6,7 +6,7 @@ def setup end test 'host_should_have_dhcp' do - h = FactoryBot.create(:host, :with_dhcp_orchestration) + h = FactoryBot.create(:host, :with_tftp_orchestration) assert h.valid? assert h.dhcp?, 'host.dhcp? does not return true' assert_equal 1, h.dhcp_records.size @@ -50,7 +50,7 @@ def setup end test "DHCP record contains jumpstart attributes" do - h = FactoryBot.build_stubbed(:host, :with_dhcp_orchestration, + h = FactoryBot.build_stubbed(:host, :with_tftp_orchestration, :model => FactoryBot.create(:model, :vendor_class => 'Sun-Fire-V210')) h.expects(:jumpstart?).at_least_once.returns(true) h.os.expects(:dhcp_record_type).at_least_once.returns(Net::DHCP::SparcRecord) @@ -63,7 +63,7 @@ def setup end test "DHCP record contains ztp attributes" do - h = FactoryBot.build_stubbed(:host, :with_dhcp_orchestration) + h = FactoryBot.build_stubbed(:host, :with_tftp_orchestration) h.os.expects(:pxe_type).at_least_once.returns("ZTP") h.os.expects(:dhcp_record_type).at_least_once.returns(Net::DHCP::ZTPRecord) h.os.expects(:ztp_arguments).at_least_once.with(h).returns(:vendor => 'huawei', :firmware => {:core => "firmware.cc", :web => "web.7z"}) @@ -77,7 +77,7 @@ def setup end test "DHCP record fallback if ZTP OS has no ztp attributes" do - h = FactoryBot.build_stubbed(:host, :with_dhcp_orchestration) + h = FactoryBot.build_stubbed(:host, :with_tftp_orchestration) h.os.expects(:pxe_type).at_least_once.returns("ZTP") h.valid? assert_equal 1, h.provision_interface.dhcp_records.size @@ -182,7 +182,7 @@ def host_with_loader(loader) context 'host with bond interface' do let(:subnet) do - FactoryBot.build(:subnet_ipv4, :dhcp, :with_taxonomies) + FactoryBot.build(:subnet_ipv4, :dhcp, :tftp, :with_taxonomies) end let(:interfaces) do [ @@ -366,24 +366,26 @@ def host_with_loader(loader) test "when an existing host triggers a 'rebuild', its dhcp records should be updated if no dhcp records are found" do Net::DHCP::Record.any_instance.stubs(:valid?).returns(false) h = as_admin do - FactoryBot.create(:host, :with_dhcp_orchestration, :mac => "aa:bb:cc:dd:ee:f1") + FactoryBot.create(:host, :with_tftp_orchestration, :mac => "aa:bb:cc:dd:ee:f1") end h.build = true assert h.valid?, h.errors.messages.to_s - assert_equal ["dhcp_remove_aa:bb:cc:dd:ee:f1", "dhcp_create_aa:bb:cc:dd:ee:f1"], h.queue.task_ids + assert_includes h.queue.task_ids, "dhcp_remove_aa:bb:cc:dd:ee:f1" + assert_includes h.queue.task_ids, "dhcp_create_aa:bb:cc:dd:ee:f1" end test "when an existing host trigger a 'rebuild', its dhcp records should not be updated if valid dhcp records are found" do Net::DHCP::Record.any_instance.stubs(:valid?).returns(true) h = as_admin do - FactoryBot.create(:host, :with_dhcp_orchestration, :mac => "aa:bb:cc:dd:ee:f1") + FactoryBot.create(:host, :with_tftp_orchestration, :mac => "aa:bb:cc:dd:ee:f1") end h.build = true assert h.valid? assert h.errors.empty? - assert_equal ["dhcp_create_aa:bb:cc:dd:ee:f1"], h.queue.task_ids + assert_includes h.queue.task_ids, "dhcp_create_aa:bb:cc:dd:ee:f1" + assert_not_includes h.queue.task_ids, "dhcp_remove_aa:bb:cc:dd:ee:f1" end test "when an existing host change its bmc mac address, its dhcp record should be updated" do diff --git a/test/models/orchestration/tftp_test.rb b/test/models/orchestration/tftp_test.rb index f0baa984f59..adc85e0cb1b 100644 --- a/test/models/orchestration/tftp_test.rb +++ b/test/models/orchestration/tftp_test.rb @@ -5,7 +5,8 @@ class TFTPOrchestrationTest < ActiveSupport::TestCase context 'host without tftp orchestration' do setup do - @host = FactoryBot.create(:host) + os = FactoryBot.create(:rhel9) + @host = FactoryBot.create(:host, :operatingsystem => os) end test 'should not have any tftp' do @@ -30,7 +31,8 @@ class TFTPOrchestrationTest < ActiveSupport::TestCase context 'host with ipv4 tftp' do setup do - @host = FactoryBot.build_stubbed(:host, :managed, :with_tftp_orchestration, :build => true) + os = FactoryBot.create(:rhel9) + @host = FactoryBot.build_stubbed(:host, :managed, :with_tftp_orchestration, :build => true, :operatingsystem => os) end test 'should have tftp' do @@ -62,7 +64,8 @@ class TFTPOrchestrationTest < ActiveSupport::TestCase context 'host with ipv6 tftp' do setup do - @host = FactoryBot.build_stubbed(:host, :managed, :with_tftp_v6_orchestration, :build => true) + os = FactoryBot.create(:rhel9) + @host = FactoryBot.build_stubbed(:host, :managed, :with_tftp_v6_orchestration, :build => true, :operatingsystem => os) end test "should have ipv6 tftp" do @@ -89,7 +92,8 @@ class TFTPOrchestrationTest < ActiveSupport::TestCase context 'host with ipv4 and ipv6 tftp' do setup do - @host = FactoryBot.build_stubbed(:host, :managed, :with_tftp_dual_stack_orchestration, :build => true) + os = FactoryBot.create(:rhel9) + @host = FactoryBot.build_stubbed(:host, :managed, :with_tftp_dual_stack_orchestration, :build => true, :operatingsystem => os) end test "host should have ipv4 and ipv6 tftp" do @@ -145,6 +149,9 @@ class TFTPOrchestrationTest < ActiveSupport::TestCase ), ] end + let(:os) do + FactoryBot.create(:rhel9) + end let(:host) do FactoryBot.create(:host, :with_tftp_orchestration, @@ -152,19 +159,36 @@ class TFTPOrchestrationTest < ActiveSupport::TestCase :interfaces => interfaces, :build => true, :location => subnet.locations.first, - :organization => subnet.organizations.first) + :organization => subnet.organizations.first, + :operatingsystem => os) end test '#setTFTP should provision tftp for all bond child macs' do ProxyAPI::TFTP.any_instance.expects(:set).with( 'PXEGrub2', '00:53:67:ab:dd:00', - :pxeconfig => 'Template' + { + :pxeconfig => 'Template', + :targetos => os.name.downcase.to_s, + :major => host.operatingsystem.major, + :minor => host.operatingsystem.minor, + :arch => host.arch.name, + :bootfilename_efi => host.arch.bootfilename_efi, + :build => host.build?, + } ).once ProxyAPI::TFTP.any_instance.expects(:set).with( 'PXEGrub2', '00:53:67:ab:dd:01', - :pxeconfig => 'Template' + { + :pxeconfig => 'Template', + :targetos => os.name.downcase.to_s, + :major => host.operatingsystem.major, + :minor => host.operatingsystem.minor, + :arch => host.arch.name, + :bootfilename_efi => host.arch.bootfilename_efi, + :build => host.build?, + } ).once host.provision_interface.stubs(:generate_pxe_template).returns('Template') host.provision_interface.send(:setTFTP, 'PXEGrub2')