ipxe: add --timeout parameter to kernel and initrd

In case of network issue during the download of the ramdisk or the
kernel, ipxe can freeze. The default timeout value is "unlimited" [0] [1].
If force_power_state_during_sync is False, ironic will just ignore the
node until someone do an human intervention.

By setting a timeout, we ensure ipxe will just give up. Depending on the
BIOS configuration, a new boot attempt may be done immedialey without the
hassle of a hard reboot.

https://bugzilla.redhat.com/show_bug.cgi?id=1310778

[0] http://ipxe.org/cmd/kernel
[1] http://lists.ipxe.org/pipermail/ipxe-devel/2014-October/003829.html

Closes-Bug: 1550417
Change-Id: I472dfb73044df50849c9cf72de90e59151698376
This commit is contained in:
Gonéri Le Bouder 2016-02-23 16:01:41 -05:00
parent cc66d0b711
commit 404bfda982
7 changed files with 76 additions and 7 deletions

View File

@ -1943,6 +1943,10 @@
# file. (string value) # file. (string value)
#ipxe_boot_script=$pybasedir/drivers/modules/boot.ipxe #ipxe_boot_script=$pybasedir/drivers/modules/boot.ipxe
# Timeout value (in seconds) for downloading an image via
# iPXE. Defaults to 0 (no timeout) (integer value)
#ipxe_timeout=0
# The IP version that will be used for PXE booting. Can be # The IP version that will be used for PXE booting. Can be
# either 4 or 6. Defaults to 4. EXPERIMENTAL (string value) # either 4 or 6. Defaults to 4. EXPERIMENTAL (string value)
#ip_version=4 #ip_version=4

View File

@ -5,14 +5,14 @@ dhcp
goto deploy goto deploy
:deploy :deploy
kernel {{ pxe_options.deployment_aki_path }} selinux=0 disk={{ pxe_options.disk }} iscsi_target_iqn={{ pxe_options.iscsi_target_iqn }} deployment_id={{ pxe_options.deployment_id }} deployment_key={{ pxe_options.deployment_key }} ironic_api_url={{ pxe_options.ironic_api_url }} troubleshoot=0 text {{ pxe_options.pxe_append_params|default("", true) }} boot_option={{ pxe_options.boot_option }} ip=${ip}:${next-server}:${gateway}:${netmask} BOOTIF=${mac} {% if pxe_options.root_device %}root_device={{ pxe_options.root_device }}{% endif %} ipa-api-url={{ pxe_options['ipa-api-url'] }} ipa-driver-name={{ pxe_options['ipa-driver-name'] }} boot_mode={{ pxe_options['boot_mode'] }} initrd=deploy_ramdisk coreos.configdrive=0 kernel {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ pxe_options.deployment_aki_path }} selinux=0 disk={{ pxe_options.disk }} iscsi_target_iqn={{ pxe_options.iscsi_target_iqn }} deployment_id={{ pxe_options.deployment_id }} deployment_key={{ pxe_options.deployment_key }} ironic_api_url={{ pxe_options.ironic_api_url }} troubleshoot=0 text {{ pxe_options.pxe_append_params|default("", true) }} boot_option={{ pxe_options.boot_option }} ip=${ip}:${next-server}:${gateway}:${netmask} BOOTIF=${mac} {% if pxe_options.root_device %}root_device={{ pxe_options.root_device }}{% endif %} ipa-api-url={{ pxe_options['ipa-api-url'] }} ipa-driver-name={{ pxe_options['ipa-driver-name'] }} boot_mode={{ pxe_options['boot_mode'] }} initrd=deploy_ramdisk coreos.configdrive=0
initrd {{ pxe_options.deployment_ari_path }} initrd {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ pxe_options.deployment_ari_path }}
boot boot
:boot_partition :boot_partition
kernel {{ pxe_options.aki_path }} root={{ ROOT }} ro text {{ pxe_options.pxe_append_params|default("", true) }} initrd=ramdisk kernel {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ pxe_options.aki_path }} root={{ ROOT }} ro text {{ pxe_options.pxe_append_params|default("", true) }} initrd=ramdisk
initrd {{ pxe_options.ari_path }} initrd {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ pxe_options.ari_path }}
boot boot
:boot_whole_disk :boot_whole_disk

View File

@ -81,6 +81,10 @@ pxe_opts = [
'drivers/modules/boot.ipxe'), 'drivers/modules/boot.ipxe'),
help=_('On ironic-conductor node, the path to the main iPXE ' help=_('On ironic-conductor node, the path to the main iPXE '
'script file.')), 'script file.')),
cfg.IntOpt('ipxe_timeout',
default=0,
help=_('Timeout value (in seconds) for downloading an image '
'via iPXE. Defaults to 0 (no timeout)')),
cfg.StrOpt('ip_version', cfg.StrOpt('ip_version',
default='4', default='4',
choices=['4', '6'], choices=['4', '6'],
@ -274,7 +278,8 @@ def _build_pxe_config_options(task, pxe_info):
'pxe_append_params': _get_pxe_conf_option(task, 'pxe_append_params'), 'pxe_append_params': _get_pxe_conf_option(task, 'pxe_append_params'),
'tftp_server': CONF.pxe.tftp_server, 'tftp_server': CONF.pxe.tftp_server,
'aki_path': kernel, 'aki_path': kernel,
'ari_path': ramdisk 'ari_path': ramdisk,
'ipxe_timeout': CONF.pxe.ipxe_timeout * 1000
} }
return pxe_options return pxe_options

View File

@ -45,6 +45,7 @@ class TestPXEUtils(db_base.DbTestCase):
u'f33c123/deploy_ramdisk', u'f33c123/deploy_ramdisk',
'root_device': 'vendor=fake,size=123', 'root_device': 'vendor=fake,size=123',
'ipa-api-url': 'http://192.168.122.184:6385', 'ipa-api-url': 'http://192.168.122.184:6385',
'ipxe_timeout': 0,
} }
self.pxe_options = { self.pxe_options = {
@ -89,6 +90,11 @@ class TestPXEUtils(db_base.DbTestCase):
} }
self.ipxe_options_bios.update(self.ipxe_options) self.ipxe_options_bios.update(self.ipxe_options)
self.ipxe_options_timeout = self.ipxe_options_bios.copy()
self.ipxe_options_timeout.update({
'ipxe_timeout': 120
})
self.ipxe_options_uefi = { self.ipxe_options_uefi = {
'boot_mode': 'uefi', 'boot_mode': 'uefi',
} }
@ -137,6 +143,25 @@ class TestPXEUtils(db_base.DbTestCase):
self.assertEqual(six.text_type(expected_template), rendered_template) self.assertEqual(six.text_type(expected_template), rendered_template)
def test__build_ipxe_timeout_config(self):
# NOTE(lucasagomes): iPXE is just an extension of the PXE driver,
# it doesn't have it's own configuration option for template.
# More info:
# http://docs.openstack.org/developer/ironic/deploy/install-guide.html
self.config(
pxe_config_template='ironic/drivers/modules/ipxe_config.template',
group='pxe'
)
self.config(http_url='http://1.2.3.4:1234', group='deploy')
rendered_template = pxe_utils._build_pxe_config(
self.ipxe_options_timeout, CONF.pxe.pxe_config_template,
'{{ ROOT }}', '{{ DISK_IDENTIFIER }}')
tpl_file = 'ironic/tests/unit/drivers/ipxe_config_timeout.template'
expected_template = open(tpl_file).read().rstrip()
self.assertEqual(six.text_type(expected_template), rendered_template)
def test__build_ipxe_uefi_config(self): def test__build_ipxe_uefi_config(self):
# NOTE(lucasagomes): iPXE is just an extension of the PXE driver, # NOTE(lucasagomes): iPXE is just an extension of the PXE driver,
# it doesn't have it's own configuration option for template. # it doesn't have it's own configuration option for template.

View File

@ -0,0 +1,19 @@
#!ipxe
dhcp
goto deploy
:deploy
kernel --timeout 120 http://1.2.3.4:1234/deploy_kernel selinux=0 disk=cciss/c0d0,sda,hda,vda iscsi_target_iqn=iqn-1be26c0b-03f2-4d2e-ae87-c02d7f33c123 deployment_id=1be26c0b-03f2-4d2e-ae87-c02d7f33c123 deployment_key=0123456789ABCDEFGHIJKLMNOPQRSTUV ironic_api_url=http://192.168.122.184:6385 troubleshoot=0 text test_param boot_option=netboot ip=${ip}:${next-server}:${gateway}:${netmask} BOOTIF=${mac} root_device=vendor=fake,size=123 ipa-api-url=http://192.168.122.184:6385 ipa-driver-name=pxe_ssh boot_mode=bios initrd=deploy_ramdisk coreos.configdrive=0
initrd --timeout 120 http://1.2.3.4:1234/deploy_ramdisk
boot
:boot_partition
kernel --timeout 120 http://1.2.3.4:1234/kernel root={{ ROOT }} ro text test_param initrd=ramdisk
initrd --timeout 120 http://1.2.3.4:1234/ramdisk
boot
:boot_whole_disk
sanboot --no-describe

View File

@ -183,11 +183,13 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
@mock.patch.object(pxe_utils, '_build_pxe_config', autospec=True) @mock.patch.object(pxe_utils, '_build_pxe_config', autospec=True)
def _test_build_pxe_config_options(self, build_pxe_mock, def _test_build_pxe_config_options(self, build_pxe_mock,
whle_dsk_img=False, whle_dsk_img=False,
ipxe_enabled=False): ipxe_enabled=False,
ipxe_timeout=0):
self.config(pxe_append_params='test_param', group='pxe') self.config(pxe_append_params='test_param', group='pxe')
# NOTE: right '/' should be removed from url string # NOTE: right '/' should be removed from url string
self.config(api_url='http://192.168.122.184:6385', group='conductor') self.config(api_url='http://192.168.122.184:6385', group='conductor')
self.config(disk_devices='sda', group='pxe') self.config(disk_devices='sda', group='pxe')
self.config(ipxe_timeout=ipxe_timeout, group='pxe')
driver_internal_info = self.node.driver_internal_info driver_internal_info = self.node.driver_internal_info
driver_internal_info['is_whole_disk_image'] = whle_dsk_img driver_internal_info['is_whole_disk_image'] = whle_dsk_img
@ -223,6 +225,8 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
ramdisk = 'no_ramdisk' ramdisk = 'no_ramdisk'
kernel = 'no_kernel' kernel = 'no_kernel'
ipxe_timeout_in_ms = ipxe_timeout * 1000
expected_options = { expected_options = {
'ari_path': ramdisk, 'ari_path': ramdisk,
'deployment_ari_path': deploy_ramdisk, 'deployment_ari_path': deploy_ramdisk,
@ -230,6 +234,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
'aki_path': kernel, 'aki_path': kernel,
'deployment_aki_path': deploy_kernel, 'deployment_aki_path': deploy_kernel,
'tftp_server': tftp_server, 'tftp_server': tftp_server,
'ipxe_timeout': ipxe_timeout_in_ms,
} }
image_info = {'deploy_kernel': ('deploy_kernel', image_info = {'deploy_kernel': ('deploy_kernel',
@ -268,6 +273,11 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
self._test_build_pxe_config_options(whle_dsk_img=False, self._test_build_pxe_config_options(whle_dsk_img=False,
ipxe_enabled=False) ipxe_enabled=False)
def test__build_pxe_config_options_ipxe_and_ipxe_timeout(self):
self._test_build_pxe_config_options(whle_dsk_img=True,
ipxe_enabled=True,
ipxe_timeout=120)
@mock.patch.object(pxe_utils, '_build_pxe_config', autospec=True) @mock.patch.object(pxe_utils, '_build_pxe_config', autospec=True)
def test__build_pxe_config_options_whole_disk_image(self, def test__build_pxe_config_options_whole_disk_image(self,
build_pxe_mock, build_pxe_mock,
@ -303,6 +313,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
'tftp_server': tftp_server, 'tftp_server': tftp_server,
'aki_path': 'no_kernel', 'aki_path': 'no_kernel',
'ari_path': 'no_ramdisk', 'ari_path': 'no_ramdisk',
'ipxe_timeout': 0,
} }
image_info = {'deploy_kernel': ('deploy_kernel', image_info = {'deploy_kernel': ('deploy_kernel',
@ -345,7 +356,8 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
'pxe_append_params': 'my-pxe-append-params', 'pxe_append_params': 'my-pxe-append-params',
'tftp_server': 'my-tftp-server', 'tftp_server': 'my-tftp-server',
'aki_path': 'no_kernel', 'aki_path': 'no_kernel',
'ari_path': 'no_ramdisk'} 'ari_path': 'no_ramdisk',
'ipxe_timeout': 0}
self.assertEqual(expected_options, options) self.assertEqual(expected_options, options)
@mock.patch.object(deploy_utils, 'fetch_images', autospec=True) @mock.patch.object(deploy_utils, 'fetch_images', autospec=True)

View File

@ -0,0 +1,4 @@
---
features:
- Add the ability to adjust ipxe timeout during image downloading, default is
still unlimited (0).