Skip to content

Commit 52b0803

Browse files
author
Bastian Schmidt
committed
Fixes #35269 - Support system image download for installation media
* Include proxy.fetch_system_image * Add system_image_path variable for template reference * Adapt PXELinux template * Add custom timeout for tftp requests * Add tftp_http_port setting
1 parent 64a2ff8 commit 52b0803

File tree

8 files changed

+114
-19
lines changed

8 files changed

+114
-19
lines changed

app/models/concerns/orchestration/tftp.rb

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,34 @@ def setTFTPBootFiles
111111
logger.info "Fetching required TFTP boot files for #{host.name}"
112112
valid = []
113113

114-
host.operatingsystem.pxe_files(host.medium_provider).each do |bootfile_info|
115-
bootfile_info.each do |prefix, path|
116-
valid << each_unique_feasible_tftp_proxy do |proxy|
117-
proxy.fetch_boot_file(:prefix => prefix.to_s, :path => path)
114+
# Check host.medium_provider path for iso image
115+
prefetch_image = File.extname(host.medium_uri.to_s).downcase.end_with?(".iso")
116+
117+
valid << each_unique_feasible_tftp_proxy do |proxy|
118+
bootfiles = host.operatingsystem.pxe_files(host.medium_provider)
119+
# fetch iso image if given
120+
if prefetch_image
121+
retries = 10
122+
pause_until_retry = 18.seconds
123+
host_url = host.medium_uri.to_s
124+
file_paths, tftp_base_path = pxe_url_to_path(bootfiles, host_url)
125+
system_image_path = host.operatingsystem.system_image_path(host.medium_provider, host, true, false)
126+
image_status = proxy.fetch_system_image(:url => host_url, :path => system_image_path, :files => file_paths, :tftp_path => tftp_base_path)
127+
until retries <= 0 || image_status == 200
128+
sleep(pause_until_retry)
129+
image_status = proxy.fetch_system_image(:url => host_url, :path => system_image_path, :files => file_paths, :tftp_path => tftp_base_path)
130+
retries = retries - 1
131+
end
132+
image_status == 200 ? true : false
133+
else
134+
bootfiles.each do |bootfile_info|
135+
bootfile_info.each do |prefix, path|
136+
proxy.fetch_boot_file(:prefix => prefix.to_s, :path => path)
137+
end
118138
end
119139
end
120140
end
141+
121142
failure _("Failed to fetch boot files") unless valid.all?
122143
valid.all?
123144
end
@@ -197,4 +218,21 @@ def each_unique_feasible_tftp_proxy
197218
end
198219
results.all?
199220
end
221+
222+
def pxe_url_to_path(pxe_urls, host_url)
223+
pxe_paths = []
224+
pxe_urls.each {|pxe_url| pxe_paths.append(pxe_url.values.first.delete_prefix(host_url))}
225+
<<<<<<< HEAD
226+
pxe_paths
227+
end
228+
229+
def pxe_replace_proxy_addr(url, host, proxy)
230+
proxy_path = host.operatingsystem.system_image_path(host.medium_provider, host, false)
231+
proxy_url = "http://#{URI.parse(proxy.url).host}/#{proxy_path}"
232+
url.sub(host.medium_uri.to_s, proxy_url)
233+
=======
234+
return pxe_paths, pxe_urls[0].keys[0]
235+
>>>>>>> 893b12df3 (Fixes #35269 - Support system image download as installation media)
236+
end
237+
200238
end

app/models/operatingsystem.rb

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class Operatingsystem < ApplicationRecord
9292
property :password_hash, String, desc: 'Encrypted hash of the operating system password'
9393
end
9494
class Jail < Safemode::Jail
95-
allow :id, :name, :major, :minor, :family, :to_s, :==, :release, :release_name, :kernel, :initrd, :pxe_type, :boot_files_uri, :password_hash, :mediumpath, :bootfile
95+
allow :id, :name, :major, :minor, :family, :to_s, :==, :release, :release_name, :kernel, :initrd, :pxe_type, :boot_files_uri, :password_hash, :mediumpath, :bootfile, :system_image_path
9696
end
9797

9898
def self.title_name
@@ -236,6 +236,26 @@ def bootfile(medium_provider, type)
236236
pxe_prefix(medium_provider) + "-" + pxe_file_names(medium_provider)[type.to_sym]
237237
end
238238

239+
apipie :method, 'Returns path to boot image based on given medium provider and (optional) host' do
240+
required :medium_provider, 'MediumProviders::Provider', 'Medium provider responsible to provide location of installation medium for a given entity (host or host group)'
241+
optional :host, 'Host::Managed', 'A specific host which can set custom a boot image path'
242+
returns String, 'Path to the boot image file'
243+
end
244+
def system_image_path(medium_provider, host = nil, include_suffix = true, include_base_path = true)
245+
unless medium_provider.is_a? MediumProviders::Provider
246+
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)'))
247+
end
248+
include_base_path ? base_path = system_image_base_path : base_path = ""
249+
include_suffix ? suffix = ".iso" : suffix = ""
250+
251+
"#{base_path}#{name.downcase}/#{medium_provider.unique_id}#{suffix}"
252+
end
253+
254+
# Base path for system_image url
255+
def system_image_base_path
256+
"/tftp/system_image/"
257+
end
258+
239259
# Does this OS family support a build variant that is constructed from a prebuilt archive
240260
def supports_image
241261
false

app/models/smart_proxy.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,14 @@ def setting(feature, setting)
109109
smart_proxy_feature_by_name(feature).try(:settings).try(:[], setting)
110110
end
111111

112+
def tftp_http_port
113+
setting(:TFTP, 'http_port')
114+
end
115+
116+
def tftp_http_port!
117+
tftp_http_port || raise(::Foreman::Exception.new(N_("HTTP boot requires proxy with httpboot feature and http_port exposed setting")))
118+
end
119+
112120
def httpboot_http_port
113121
setting(:HTTPBoot, 'http_port')
114122
end
@@ -202,12 +210,14 @@ def get_features
202210
sections only: %w[all additional]
203211
prop_group :basic_model_props, ApplicationRecord, meta: { friendly_name: 'Smart Proxy' }
204212
property :hostname, String, desc: 'Returns name of the host with proxy'
213+
property :tftp_http_port, Integer, desc: 'Returns proxy port for TFTP boot images'
214+
property :tftp_http_port!, Integer, desc: 'Same as tftp_http_port, but raises Foreman::Exception if no port is set'
205215
property :httpboot_http_port, Integer, desc: 'Returns proxy port for HTTP boot'
206216
property :httpboot_http_port!, Integer, desc: 'Same as httpboot_http_port, but raises Foreman::Exception if no port is set'
207217
property :httpboot_https_port, Integer, desc: 'Returns proxy port for HTTPS boot'
208218
property :httpboot_https_port!, Integer, desc: 'Same as httpboot_https_port, but raises Foreman::Exception if no port is set'
209219
end
210220
class Jail < ::Safemode::Jail
211-
allow :id, :name, :hostname, :httpboot_http_port, :httpboot_https_port, :httpboot_http_port!, :httpboot_https_port!, :url
221+
allow :id, :name, :hostname, :tftp_http_port, :httpboot_http_port, :httpboot_https_port, :tftp_http_port!, :httpboot_http_port!, :httpboot_https_port!, :url
212222
end
213223
end

app/services/foreman/renderer/configuration.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ class Configuration
100100
:static,
101101
:template_name,
102102
:xen,
103+
:system_image_path,
103104
]
104105

105106
DEFAULT_ALLOWED_GLOBAL_SETTINGS = [

app/services/foreman/renderer/scope/variables/base.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def self.included(base)
1212
delegate :diskLayout, :disk_layout_source, :medium, :architecture, :ptable, :use_image, :arch,
1313
:image_file, :default_image_file, to: :host, allow_nil: true
1414
delegate :mediumpath, :additional_media, :supports_image, :major, :preseed_path, :preseed_server,
15-
:xen, :kernel, :initrd, to: :operatingsystem, allow_nil: true
15+
:xen, :kernel, :initrd, :system_image_path, to: :operatingsystem, allow_nil: true
1616
delegate :name, to: :architecture, allow_nil: true, prefix: true
1717
delegate :content, to: :disk_layout_source, allow_nil: true, prefix: true
1818

@@ -97,6 +97,7 @@ def xenserver_attributes
9797

9898
def pxe_config
9999
return unless @medium_provider
100+
@system_image_path = system_image_path(@medium_provider, host)
100101
@kernel = kernel(@medium_provider)
101102
@initrd = initrd(@medium_provider)
102103
@kernel_uri, @initrd_uri = operatingsystem.boot_files_uri(@medium_provider)

app/services/proxy_api/resource.rb

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@ def initialize(args)
2828

2929
attr_reader :connect_params
3030

31-
def resource
31+
def resource(timeout = nil)
3232
# Required in order to ability to mock the resource
33+
unless timeout.nil?
34+
custom_params = connect_params.merge(timeout: timeout)
35+
return RestClient::Resource.new(url, custom_params)
36+
end
3337
@resource ||= RestClient::Resource.new(url, connect_params)
3438
end
3539

@@ -65,7 +69,7 @@ def parse(response)
6569
end
6670

6771
# Perform GET operation on the supplied path
68-
def get(path = nil, payload = {})
72+
def get(path = nil, payload = {}, timeout = nil)
6973
query = payload.delete(:query)
7074
Foreman::Deprecation.deprecation_warning("3.3", "passing additional headers to ProxyApi resource GET action") unless payload.empty?
7175
final_uri = path || ""
@@ -78,39 +82,39 @@ def get(path = nil, payload = {})
7882
telemetry_duration_histogram(:proxy_api_duration, :ms, method: 'get') do
7983
# This ensures that an extra "/" is not generated
8084
if path
81-
resource[final_uri].get payload
85+
resource(timeout)[final_uri].get payload
8286
else
83-
resource.get payload
87+
resource(timeout).get payload
8488
end
8589
end
8690
end
8791
end
8892

8993
# Perform POST operation with the supplied payload on the supplied path
90-
def post(payload, path = "")
94+
def post(payload, path = "", timeout = nil)
9195
logger.debug("POST request payload: #{payload}")
9296
with_logger do
9397
telemetry_duration_histogram(:proxy_api_duration, :ms, method: 'post') do
94-
resource[path].post payload
98+
resource(timeout)[path].post payload
9599
end
96100
end
97101
end
98102

99103
# Perform PUT operation with the supplied payload on the supplied path
100-
def put(payload, path = "")
104+
def put(payload, path = "", timeout = nil)
101105
logger.debug("PUT request payload: #{payload}")
102106
with_logger do
103107
telemetry_duration_histogram(:proxy_api_duration, :ms, method: 'put') do
104-
resource[path].put payload
108+
resource(timeout)[path].put payload
105109
end
106110
end
107111
end
108112

109113
# Perform DELETE operation on the supplied path
110-
def delete(path)
114+
def delete(path, timeout = nil)
111115
with_logger do
112116
telemetry_duration_histogram(:proxy_api_duration, :ms, method: 'delete') do
113-
resource[path].delete
117+
resource(timeout)[path].delete
114118
end
115119
end
116120
end

app/services/proxy_api/tftp.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,20 @@ def fetch_boot_file(args)
3838
raise ProxyException.new(url, e, N_("Unable to fetch TFTP boot file"))
3939
end
4040

41+
# Requests that the proxy downloads and extracts an image from the media's source
42+
# [+args+] : Hash containing
43+
# :path => String containing the location on the smart proxy to store the image
44+
# :url => String containing the URL of the image to download
45+
# Returns : Integer response status
46+
def fetch_system_image(args)
47+
response = post(args, "fetch_system_image")
48+
response.code
49+
rescue RestClient::Locked
50+
423
51+
rescue => e
52+
raise ProxyException.new(url, e, N_("Unable to fetch and extract TFTP system image"))
53+
end
54+
4155
# returns the TFTP boot server for this proxy
4256
def bootServer
4357
if (response = parse(get("serverName"))) && response["serverName"].present?

app/views/unattended/provisioning_templates/PXELinux/preseed_default_pxelinux_autoinstall.erb

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,14 @@ test_on:
4242

4343
options << "locale=#{host_param('lang') || 'en_US'}"
4444
options = options.join(' ')
45-
image_path = @preseed_path.sub(/\/?$/, '.iso')
45+
if @preseed_path.downcase.end_with?('.iso')
46+
image_path = @system_image_path
47+
tftp = @host.subnet.tftp
48+
image_host = "#{tftp}:#{tftp.tftp_http_port}"
49+
else
50+
image_path = @preseed_path.sub(/\/?$/, '.iso')
51+
image_host = foreman_request_addr.split(':').first
52+
end
4653
-%>
4754
#
4855
# WARNING
@@ -55,6 +62,6 @@ DEFAULT linux cloud-init autoinstall
5562
LABEL linux cloud-init autoinstall
5663
KERNEL <%= @kernel %>
5764
INITRD <%= @initrd %>
58-
APPEND 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 <%= options %>
65+
APPEND url=http://<%= image_host %><%= image_path %> autoinstall ds=nocloud-net;s=http://<%= foreman_request_addr %>/userdata/ root=/dev/ram0 ramdisk_size=1500000 fsck.mode=skip <%= options %>
5966

6067
<%= snippet_if_exists(template_name + " custom menu") %>

0 commit comments

Comments
 (0)