diff --git a/modules/tftp/server.rb b/modules/tftp/server.rb index 98218d8bd..a972d3664 100644 --- a/modules/tftp/server.rb +++ b/modules/tftp/server.rb @@ -63,6 +63,19 @@ def delete_file(file) logger.debug "TFTP: Skipping a request to delete a file which doesn't exists" end end + + def delete_host_dir(mac) + host_dir = File.join(path, 'host-config', dashed_mac(mac).downcase) + logger.debug "TFTP: Removing directory '#{host_dir}'." + FileUtils.rm_rf host_dir + end + + def setup_bootloader(mac:, os:, release:, arch:, bootfilename_efi:) + end + + def dashed_mac(mac) + mac.tr(':', '-') + end end class Syslinux < Server @@ -75,7 +88,7 @@ def pxe_default end def pxeconfig_file(mac) - ["#{pxeconfig_dir}/01-" + mac.tr(':', "-").downcase] + ["#{pxeconfig_dir}/01-" + dashed_mac(mac).downcase] end end class Pxelinux < Syslinux; end @@ -90,13 +103,99 @@ def pxe_default end def pxeconfig_file(mac) - ["#{pxeconfig_dir}/menu.lst.01" + mac.delete(':').upcase, "#{pxeconfig_dir}/01-" + mac.tr(':', '-').upcase] + ["#{pxeconfig_dir}/menu.lst.01" + mac.delete(':').upcase, "#{pxeconfig_dir}/01-" + dashed_mac(mac).upcase] end end class Pxegrub2 < Server - def pxeconfig_dir - "#{path}/grub2" + def bootloader_path(os, release, arch) + bootloader_path = File.join(Proxy::TFTP::Plugin.settings.tftproot, 'bootloader-universe/pxegrub2', os, release, arch) + + logger.debug "TFTP: Checking if bootloader universe is configured for OS '#{os}' version '#{release}' (#{arch})." + logger.debug "TFTP: Checking if directory '#{bootloader_path}' exists." + unless Dir.exist?(bootloader_path) + logger.debug "TFTP: Directory '#{bootloader_path}' does not exist." + + bootloader_path = File.join(Proxy::TFTP::Plugin.settings.tftproot, 'bootloader-universe/pxegrub2', os, 'default', arch) + + logger.debug "TFTP: Checking if bootloader universe is configured for OS '#{os}' (#{arch})." + logger.debug "TFTP: Checking if directory '#{bootloader_path}' exists." + unless Dir.exist?(bootloader_path) + logger.debug "TFTP: Directory '#{bootloader_path}' does not exist." + return + end + end + + bootloader_path + end + + def create_bootloader_universe_symlinks(bootloader_path, pxeconfig_dir_mac) + symlinks = [] + Dir.glob(File.join(bootloader_path, '*.efi')).each do |source_file| + symlinks << { source: Pathname.new(source_file), symlink: Pathname.new(File.join(pxeconfig_dir_mac, File.basename(source_file))) } + end + create_symlinks(symlinks) + end + + def create_default_symlinks(bootfilename_efi, pxeconfig_dir_mac) + pxeconfig_dir = pxeconfig_dir() + symlinks = [ + { source: Pathname.new(File.join(pxeconfig_dir, "grub#{bootfilename_efi}.efi")), symlink: Pathname.new(File.join(pxeconfig_dir_mac, "boot.efi")) }, + { source: Pathname.new(File.join(pxeconfig_dir, "grub#{bootfilename_efi}.efi")), symlink: Pathname.new(File.join(pxeconfig_dir_mac, "grub#{bootfilename_efi}.efi")) }, + { source: Pathname.new(File.join(pxeconfig_dir, "shim#{bootfilename_efi}.efi")), symlink: Pathname.new(File.join(pxeconfig_dir_mac, "boot-sb.efi")) }, + { source: Pathname.new(File.join(pxeconfig_dir, "shim#{bootfilename_efi}.efi")), symlink: Pathname.new(File.join(pxeconfig_dir_mac, "shim#{bootfilename_efi}.efi")) }, + ] + create_symlinks(symlinks) + end + + def create_symlinks(symlinks) + symlinks.each do |link| + relative_source_path = link[:source].relative_path_from(link[:symlink].parent) + + logger.debug "TFTP: Creating relative symlink: #{link[:symlink]} -> #{relative_source_path}" + FileUtils.ln_s(relative_source_path.to_s, link[:symlink].to_s, force: true) + end + end + + # Configures bootloader files for a host in its host-config directory + # + # @param mac [String] The MAC address of the host + # @param os [String] The lowercase name of the operating system of the host + # @param release [String] The major and minor version of the operating system of the host + # @param arch [String] The architecture of the operating system of the host + # @param bootfilename_efi [String] The architecture specific boot filename suffix + def setup_bootloader(mac:, os:, release:, arch:, bootfilename_efi:) + pxeconfig_dir_mac = pxeconfig_dir(mac) + + logger.debug "TFTP: Deploying host specific bootloader files to '#{pxeconfig_dir_mac}'." + + FileUtils.mkdir_p(pxeconfig_dir_mac) + FileUtils.rm_f(Dir.glob("#{pxeconfig_dir_mac}/*.efi")) + + bootloader_path = bootloader_path(os, release, arch) + + if bootloader_path + logger.debug "TFTP: Creating symlinks from bootloader universe." + create_bootloader_universe_symlinks(bootloader_path, pxeconfig_dir_mac) + else + logger.debug "TFTP: Creating symlinks from default bootloader files." + create_default_symlinks(bootfilename_efi, pxeconfig_dir_mac) + end + + File.write(File.join(pxeconfig_dir_mac, 'os_info'), "#{os} #{release} #{arch}") + end + + def del(mac) + super mac + delete_host_dir mac + end + + def pxeconfig_dir(mac = nil) + if mac + File.join(path, 'host-config', dashed_mac(mac).downcase, 'grub2') + else + File.join(path, 'grub2') + end end def pxe_default @@ -104,7 +203,14 @@ def pxe_default end def pxeconfig_file(mac) - ["#{pxeconfig_dir}/grub.cfg-01-" + mac.tr(':', '-').downcase, "#{pxeconfig_dir}/grub.cfg-#{mac.downcase}"] + pxeconfig_dir_mac = pxeconfig_dir(mac) + [ + "#{pxeconfig_dir_mac}/grub.cfg", + "#{pxeconfig_dir_mac}/grub.cfg-01-#{dashed_mac(mac).downcase}", + "#{pxeconfig_dir_mac}/grub.cfg-#{mac.downcase}", + "#{pxeconfig_dir}/grub.cfg-01-" + dashed_mac(mac).downcase, + "#{pxeconfig_dir}/grub.cfg-#{mac.downcase}", + ] end end @@ -146,7 +252,7 @@ def pxe_default end def pxeconfig_file(mac) - ["#{pxeconfig_dir}/01-" + mac.tr(':', "-").downcase + ".ipxe"] + ["#{pxeconfig_dir}/01-" + dashed_mac(mac).downcase + ".ipxe"] end end diff --git a/modules/tftp/tftp_api.rb b/modules/tftp/tftp_api.rb index 1bc6276c7..682fb922b 100644 --- a/modules/tftp/tftp_api.rb +++ b/modules/tftp/tftp_api.rb @@ -18,8 +18,9 @@ def instantiate(variant, mac = nil) Object.const_get("Proxy").const_get('TFTP').const_get(variant.capitalize).new end - def create(variant, mac) + def create(variant, mac, os: nil, release: nil, arch: nil, bootfilename_efi: nil) tftp = instantiate variant, mac + log_halt(400, "TFTP: Failed to setup host specific bootloader directory: ") { tftp.setup_bootloader(mac: mac, os: os, release: release, arch: arch, bootfilename_efi: bootfilename_efi) } log_halt(400, "TFTP: Failed to create pxe config file: ") { tftp.set(mac, (params[:pxeconfig] || params[:syslinux_config])) } end @@ -48,7 +49,7 @@ def create_default(variant) end post "/:variant/:mac" do |variant, mac| - create variant, mac + create variant, mac, os: params[:targetos], release: params[:release], arch: params[:arch], bootfilename_efi: params[:bootfilename_efi] end delete "/:variant/:mac" do |variant, mac| diff --git a/modules/tftp/tftp_plugin.rb b/modules/tftp/tftp_plugin.rb index d4dc06450..bb92bed53 100644 --- a/modules/tftp/tftp_plugin.rb +++ b/modules/tftp/tftp_plugin.rb @@ -2,6 +2,8 @@ module Proxy::TFTP class Plugin < ::Proxy::Plugin plugin :tftp, ::Proxy::VERSION + capability :bootloader_universe + rackup_path File.expand_path("http_config.ru", __dir__) default_settings :tftproot => '/var/lib/tftpboot', diff --git a/test/tftp/integration_test.rb b/test/tftp/integration_test.rb index a9b88a8f8..9b621e7f9 100644 --- a/test/tftp/integration_test.rb +++ b/test/tftp/integration_test.rb @@ -14,7 +14,7 @@ def test_features mod = response['tftp'] refute_nil(mod) assert_equal('running', mod['state'], Proxy::LogBuffer::Buffer.instance.info[:failed_modules][:tftp]) - assert_equal([], mod['capabilities']) + assert_equal(["bootloader_universe"], mod['capabilities']) expected_settings = { 'tftp_servername' => 'tftp.example.com' } assert_equal(expected_settings, mod['settings']) diff --git a/test/tftp/tftp_server_test.rb b/test/tftp/tftp_server_test.rb index 42d6f1071..ac9d848ce 100644 --- a/test/tftp/tftp_server_test.rb +++ b/test/tftp/tftp_server_test.rb @@ -113,7 +113,13 @@ class TftpPxegrub2ServerTest < Test::Unit::TestCase def setup_paths @subject = Proxy::TFTP::Pxegrub2.new - @pxe_config_files = ["grub2/grub.cfg-01-aa-bb-cc-dd-ee-ff", "grub2/grub.cfg-aa:bb:cc:dd:ee:ff"] + @pxe_config_files = [ + "host-config/aa-bb-cc-dd-ee-ff/grub2/grub.cfg", + "host-config/aa-bb-cc-dd-ee-ff/grub2/grub.cfg-01-aa-bb-cc-dd-ee-ff", + "host-config/aa-bb-cc-dd-ee-ff/grub2/grub.cfg-aa:bb:cc:dd:ee:ff", + "grub2/grub.cfg-01-aa-bb-cc-dd-ee-ff", + "grub2/grub.cfg-aa:bb:cc:dd:ee:ff", + ] @pxe_default_files = ["grub2/grub.cfg"] end end