From bb0d0fcaddbc3452820c4e33529203f887de0b4f Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Tue, 19 Jul 2022 09:32:15 +0200 Subject: [PATCH] Fixes #35269 - Enable boot image download of installation media * Include proxy.fetch_boot_image * Add bootimage_path variable for template reference * Adapt PXELinux template * Add custom timeout for tftp requests --- app/models/concerns/orchestration/tftp.rb | 24 ++++++++++++++++--- app/models/operatingsystem.rb | 21 +++++++++++++++- .../foreman/renderer/configuration.rb | 1 + .../foreman/renderer/scope/variables/base.rb | 3 ++- app/services/proxy_api/resource.rb | 24 +++++++++++-------- app/services/proxy_api/tftp.rb | 11 +++++++++ .../preseed_default_pxelinux_autoinstall.erb | 3 +-- 7 files changed, 70 insertions(+), 17 deletions(-) diff --git a/app/models/concerns/orchestration/tftp.rb b/app/models/concerns/orchestration/tftp.rb index 2665cab80f53..46f533b257bf 100644 --- a/app/models/concerns/orchestration/tftp.rb +++ b/app/models/concerns/orchestration/tftp.rb @@ -111,13 +111,31 @@ def setTFTPBootFiles logger.info "Fetching required TFTP boot files for #{host.name}" valid = [] - host.operatingsystem.pxe_files(host.medium_provider).each do |bootfile_info| - bootfile_info.each do |prefix, path| - valid << each_unique_feasible_tftp_proxy do |proxy| + # Check host.medium_provider path for iso image + prefetch_image = host.medium_uri.to_s.end_with?(".iso") + + valid << each_unique_feasible_tftp_proxy do |proxy| + bootfiles = host.operatingsystem.pxe_files(host.medium_provider) + # fetch iso image if given + if prefetch_image + raw_files = [] + bootfiles.each {|info| raw_files.append(info.values.first.delete_prefix(host.medium_uri.to_s))} + proxy.fetch_boot_image(:url => host.medium_uri.to_s, :path => host.operatingsystem.bootimage_path(host.medium_provider, host, include_suffix=true, include_base_path=false), :files => raw_files) + end + + host.operatingsystem.pxe_files(host.medium_provider).each do |bootfile_info| + bootfile_info.each do |prefix, path| + # change path in case of iso download + if prefetch_image + proxy_path = host.operatingsystem.bootimage_path(host.medium_provider, host, false) + proxy_url = "http://#{URI.parse(proxy.url).host}/#{proxy_path}" + path.sub!(host.medium_uri.to_s, proxy_url) + end proxy.fetch_boot_file(:prefix => prefix.to_s, :path => path) end end end + failure _("Failed to fetch boot files") unless valid.all? valid.all? end diff --git a/app/models/operatingsystem.rb b/app/models/operatingsystem.rb index 2af7ebea17ba..9f0124f01c39 100644 --- a/app/models/operatingsystem.rb +++ b/app/models/operatingsystem.rb @@ -92,7 +92,7 @@ class Operatingsystem < ApplicationRecord property :password_hash, String, desc: 'Encrypted hash of the operating system password' end class Jail < Safemode::Jail - allow :id, :name, :major, :minor, :family, :to_s, :==, :release, :release_name, :kernel, :initrd, :pxe_type, :boot_files_uri, :password_hash, :mediumpath, :bootfile + allow :id, :name, :major, :minor, :family, :to_s, :==, :release, :release_name, :kernel, :initrd, :pxe_type, :boot_files_uri, :password_hash, :mediumpath, :bootfile, :bootimage_path end def self.title_name @@ -236,6 +236,25 @@ def bootfile(medium_provider, type) pxe_prefix(medium_provider) + "-" + pxe_file_names(medium_provider)[type.to_sym] end + apipie :method, 'Returns path to boot image based on given medium provider and (optional) host' do + required :medium_provider, 'MediumProviders::Provider', 'Medium provider responsible to provide location of installation medium for a given entity (host or host group)' + optional :host, 'Host::Managed', 'A specific host which can set custom a boot image path' + returns String, 'Path to the boot image file' + end + def bootimage_path(medium_provider, host = nil, include_suffix = true, include_base_path = true) + unless medium_provider.is_a? MediumProviders::Provider + raise Foreman::Exception.new(N_('Please provide a medium provider. It can be found as @medium_provider in templates, or Foreman::Plugin.medium_providers_registry.find_provider(host)')) + end + include_base_path ? base_path = "/pub/installation_media/" : base_path = "" + include_suffix ? suffix = ".iso" : suffix = "" + unless host.nil? + base_path = host.host_params["custom_bootimage_path"] if host.host_params.has_key? "custom_bootimage_path" + base_path += "/" unless base_path[-1] == '/' + end + + "#{base_path}#{name.downcase}/#{medium_provider.unique_id}#{suffix}" + end + # Does this OS family support a build variant that is constructed from a prebuilt archive def supports_image false diff --git a/app/services/foreman/renderer/configuration.rb b/app/services/foreman/renderer/configuration.rb index 5f7819b3fa3e..7a487e3179b2 100644 --- a/app/services/foreman/renderer/configuration.rb +++ b/app/services/foreman/renderer/configuration.rb @@ -99,6 +99,7 @@ class Configuration :static, :template_name, :xen, + :bootimage_path, ] DEFAULT_ALLOWED_GLOBAL_SETTINGS = [ diff --git a/app/services/foreman/renderer/scope/variables/base.rb b/app/services/foreman/renderer/scope/variables/base.rb index f211eb177ade..93e8a49e72eb 100644 --- a/app/services/foreman/renderer/scope/variables/base.rb +++ b/app/services/foreman/renderer/scope/variables/base.rb @@ -12,7 +12,7 @@ def self.included(base) delegate :diskLayout, :disk_layout_source, :medium, :architecture, :ptable, :use_image, :arch, :image_file, :default_image_file, to: :host, allow_nil: true delegate :mediumpath, :additional_media, :supports_image, :major, :preseed_path, :preseed_server, - :xen, :kernel, :initrd, to: :operatingsystem, allow_nil: true + :xen, :kernel, :initrd, :bootimage_path, to: :operatingsystem, allow_nil: true delegate :name, to: :architecture, allow_nil: true, prefix: true delegate :content, to: :disk_layout_source, allow_nil: true, prefix: true @@ -97,6 +97,7 @@ def xenserver_attributes def pxe_config return unless @medium_provider + @bootimage_path = bootimage_path(@medium_provider, host) @kernel = kernel(@medium_provider) @initrd = initrd(@medium_provider) @kernel_uri, @initrd_uri = operatingsystem.boot_files_uri(@medium_provider) diff --git a/app/services/proxy_api/resource.rb b/app/services/proxy_api/resource.rb index 088853400eb3..fed7adc6333d 100644 --- a/app/services/proxy_api/resource.rb +++ b/app/services/proxy_api/resource.rb @@ -28,8 +28,12 @@ def initialize(args) attr_reader :connect_params - def resource + def resource(timeout = nil) # Required in order to ability to mock the resource + unless timeout.nil? + custom_params = connect_params.merge(timeout: timeout) + return RestClient::Resource.new(url, custom_params) + end @resource ||= RestClient::Resource.new(url, connect_params) end @@ -65,7 +69,7 @@ def parse(response) end # Perform GET operation on the supplied path - def get(path = nil, payload = {}) + def get(path = nil, payload = {}, timeout = nil) query = payload.delete(:query) Foreman::Deprecation.deprecation_warning("3.3", "passing additional headers to ProxyApi resource GET action") unless payload.empty? final_uri = path || "" @@ -78,39 +82,39 @@ def get(path = nil, payload = {}) telemetry_duration_histogram(:proxy_api_duration, :ms, method: 'get') do # This ensures that an extra "/" is not generated if path - resource[final_uri].get payload + resource(timeout)[final_uri].get payload else - resource.get payload + resource(timeout).get payload end end end end # Perform POST operation with the supplied payload on the supplied path - def post(payload, path = "") + def post(payload, path = "", timeout = nil) logger.debug("POST request payload: #{payload}") with_logger do telemetry_duration_histogram(:proxy_api_duration, :ms, method: 'post') do - resource[path].post payload + resource(timeout)[path].post payload end end end # Perform PUT operation with the supplied payload on the supplied path - def put(payload, path = "") + def put(payload, path = "", timeout = nil) logger.debug("PUT request payload: #{payload}") with_logger do telemetry_duration_histogram(:proxy_api_duration, :ms, method: 'put') do - resource[path].put payload + resource(timeout)[path].put payload end end end # Perform DELETE operation on the supplied path - def delete(path) + def delete(path, timeout = nil) with_logger do telemetry_duration_histogram(:proxy_api_duration, :ms, method: 'delete') do - resource[path].delete + resource(timeout)[path].delete end end end diff --git a/app/services/proxy_api/tftp.rb b/app/services/proxy_api/tftp.rb index dc4f0582ca7d..66b618d7cbe4 100644 --- a/app/services/proxy_api/tftp.rb +++ b/app/services/proxy_api/tftp.rb @@ -38,6 +38,17 @@ def fetch_boot_file(args) raise ProxyException.new(url, e, N_("Unable to fetch TFTP boot file")) end + # Requests that the proxy downloads and extracts an image from the media's source + # [+args+] : Hash containing + # :path => String containing the location on the smart proxy to store the image + # :url => String containing the URL of the image to download + # Returns : Boolean status + def fetch_boot_image(args) + parse(post(args, "fetch_boot_image", 180)) # Set 180 seconds timeout for large image download + rescue => e + raise ProxyException.new(url, e, N_("Unable to fetch and extract TFTP boot image")) + end + # returns the TFTP boot server for this proxy def bootServer if (response = parse(get("serverName"))) && response["serverName"].present? diff --git a/app/views/unattended/provisioning_templates/PXELinux/preseed_default_pxelinux_autoinstall.erb b/app/views/unattended/provisioning_templates/PXELinux/preseed_default_pxelinux_autoinstall.erb index fa044ccec079..98b5d03dba54 100644 --- a/app/views/unattended/provisioning_templates/PXELinux/preseed_default_pxelinux_autoinstall.erb +++ b/app/views/unattended/provisioning_templates/PXELinux/preseed_default_pxelinux_autoinstall.erb @@ -34,7 +34,6 @@ test_on: end options << "locale=#{host_param('lang') || 'en_US'}" options = options.join(' ') - image_path = @preseed_path.sub(/\/?$/, '.iso') -%> # # WARNING @@ -47,6 +46,6 @@ DEFAULT linux cloud-init autoinstall LABEL linux cloud-init autoinstall KERNEL <%= @kernel %> INITRD <%= @initrd %> - APPEND ip=dhcp url=http://<%= @preseed_server %><%= image_path %> autoinstall ds=nocloud-net;s=http://<%= foreman_request_addr %>/userdata/ root=/dev/ram0 ramdisk_size=1500000 fsck.mode=skip + APPEND ip=dhcp url=http://<%= foreman_request_addr.split(':').first %><%= @bootimage_path %> autoinstall ds=nocloud-net;s=http://<%= foreman_request_addr %>/userdata/ root=/dev/ram0 ramdisk_size=1500000 fsck.mode=skip <%= snippet_if_exists(template_name + " custom menu") %>