diff --git a/app/models/concerns/orchestration/dhcp.rb b/app/models/concerns/orchestration/dhcp.rb index a7899c1089c..524a46de4c3 100644 --- a/app/models/concerns/orchestration/dhcp.rb +++ b/app/models/concerns/orchestration/dhcp.rb @@ -108,6 +108,19 @@ 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") + filename.gsub(/\/shim\w*\.efi$/, "/boot-sb.efi") + else + filename.gsub("@@subdir@@/", "") + end + end + 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 +137,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')