Skip to content

Commit

Permalink
Fixes #36834 - Add SecureBoot support for arbitrary operating systems…
Browse files Browse the repository at this point in the history
… to "Grub2 UEFI" PXE loaders

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
  • Loading branch information
Jan Löser authored and goarsna committed Jun 25, 2024
1 parent e9891a9 commit f18c9b3
Show file tree
Hide file tree
Showing 11 changed files with 113 additions and 40 deletions.
15 changes: 14 additions & 1 deletion app/models/concerns/orchestration/dhcp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 = filename.gsub(/\/shim\w*\.efi$/, "/boot-sb.efi")
else
filename = 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?
Expand All @@ -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
Expand Down
10 changes: 9 additions & 1 deletion app/models/concerns/orchestration/tftp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions app/models/concerns/pxe_loader_support.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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",
Expand Down
6 changes: 6 additions & 0 deletions app/services/proxy_api/tftp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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}"))
Expand Down
4 changes: 4 additions & 0 deletions test/factories/architecture.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@
trait :for_snapshots_x86_64 do
name { 'x86_64' }
end

trait :x64 do
name { 'x64' }
end
end
end
11 changes: 11 additions & 0 deletions test/factories/operatingsystem.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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' }
Expand Down
29 changes: 17 additions & 12 deletions test/models/concerns/pxe_loader_support_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions test/models/operatingsystem_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions test/models/orchestration/compute_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 11 additions & 9 deletions test/models/orchestration/dhcp_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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"})
Expand All @@ -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
Expand Down Expand Up @@ -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
[
Expand Down Expand Up @@ -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
Expand Down
38 changes: 31 additions & 7 deletions test/models/orchestration/tftp_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -145,26 +149,46 @@ class TFTPOrchestrationTest < ActiveSupport::TestCase
),
]
end
let(:os) do
FactoryBot.create(:rhel9)
end
let(:host) do
FactoryBot.create(:host,
:with_tftp_orchestration,
:subnet => subnet,
: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')
Expand Down

0 comments on commit f18c9b3

Please sign in to comment.