From 404bfda9829ee8acf57fa0ec584b01f3118c3a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A9ri=20Le=20Bouder?= Date: Tue, 23 Feb 2016 16:01:41 -0500 Subject: [PATCH] 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 --- etc/ironic/ironic.conf.sample | 4 +++ ironic/drivers/modules/ipxe_config.template | 8 +++--- ironic/drivers/modules/pxe.py | 7 +++++- ironic/tests/unit/common/test_pxe_utils.py | 25 +++++++++++++++++++ .../unit/drivers/ipxe_config_timeout.template | 19 ++++++++++++++ ironic/tests/unit/drivers/modules/test_pxe.py | 16 ++++++++++-- ...xe_timeout_parameter-03fc3c76c520fac2.yaml | 4 +++ 7 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 ironic/tests/unit/drivers/ipxe_config_timeout.template create mode 100644 releasenotes/notes/ipxe_timeout_parameter-03fc3c76c520fac2.yaml diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample index 60f180b66e..3648ca4159 100644 --- a/etc/ironic/ironic.conf.sample +++ b/etc/ironic/ironic.conf.sample @@ -1943,6 +1943,10 @@ # file. (string value) #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 # either 4 or 6. Defaults to 4. EXPERIMENTAL (string value) #ip_version=4 diff --git a/ironic/drivers/modules/ipxe_config.template b/ironic/drivers/modules/ipxe_config.template index f3aa6cf74e..d09f016836 100644 --- a/ironic/drivers/modules/ipxe_config.template +++ b/ironic/drivers/modules/ipxe_config.template @@ -5,14 +5,14 @@ dhcp goto 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_partition -kernel {{ pxe_options.aki_path }} root={{ ROOT }} ro text {{ pxe_options.pxe_append_params|default("", true) }} initrd=ramdisk -initrd {{ pxe_options.ari_path }} +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 {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ pxe_options.ari_path }} boot :boot_whole_disk diff --git a/ironic/drivers/modules/pxe.py b/ironic/drivers/modules/pxe.py index af7b5ccba7..2bbec67ae7 100644 --- a/ironic/drivers/modules/pxe.py +++ b/ironic/drivers/modules/pxe.py @@ -81,6 +81,10 @@ pxe_opts = [ 'drivers/modules/boot.ipxe'), help=_('On ironic-conductor node, the path to the main iPXE ' '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', default='4', 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'), 'tftp_server': CONF.pxe.tftp_server, 'aki_path': kernel, - 'ari_path': ramdisk + 'ari_path': ramdisk, + 'ipxe_timeout': CONF.pxe.ipxe_timeout * 1000 } return pxe_options diff --git a/ironic/tests/unit/common/test_pxe_utils.py b/ironic/tests/unit/common/test_pxe_utils.py index b8ecde7838..bb584c92d8 100644 --- a/ironic/tests/unit/common/test_pxe_utils.py +++ b/ironic/tests/unit/common/test_pxe_utils.py @@ -45,6 +45,7 @@ class TestPXEUtils(db_base.DbTestCase): u'f33c123/deploy_ramdisk', 'root_device': 'vendor=fake,size=123', 'ipa-api-url': 'http://192.168.122.184:6385', + 'ipxe_timeout': 0, } self.pxe_options = { @@ -89,6 +90,11 @@ class TestPXEUtils(db_base.DbTestCase): } 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 = { 'boot_mode': 'uefi', } @@ -137,6 +143,25 @@ class TestPXEUtils(db_base.DbTestCase): 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): # NOTE(lucasagomes): iPXE is just an extension of the PXE driver, # it doesn't have it's own configuration option for template. diff --git a/ironic/tests/unit/drivers/ipxe_config_timeout.template b/ironic/tests/unit/drivers/ipxe_config_timeout.template new file mode 100644 index 0000000000..cb82ad73a5 --- /dev/null +++ b/ironic/tests/unit/drivers/ipxe_config_timeout.template @@ -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 diff --git a/ironic/tests/unit/drivers/modules/test_pxe.py b/ironic/tests/unit/drivers/modules/test_pxe.py index 880ee2253f..7456262400 100644 --- a/ironic/tests/unit/drivers/modules/test_pxe.py +++ b/ironic/tests/unit/drivers/modules/test_pxe.py @@ -183,11 +183,13 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase): @mock.patch.object(pxe_utils, '_build_pxe_config', autospec=True) def _test_build_pxe_config_options(self, build_pxe_mock, whle_dsk_img=False, - ipxe_enabled=False): + ipxe_enabled=False, + ipxe_timeout=0): self.config(pxe_append_params='test_param', group='pxe') # NOTE: right '/' should be removed from url string self.config(api_url='http://192.168.122.184:6385', group='conductor') 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['is_whole_disk_image'] = whle_dsk_img @@ -223,6 +225,8 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase): ramdisk = 'no_ramdisk' kernel = 'no_kernel' + ipxe_timeout_in_ms = ipxe_timeout * 1000 + expected_options = { 'ari_path': ramdisk, 'deployment_ari_path': deploy_ramdisk, @@ -230,6 +234,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase): 'aki_path': kernel, 'deployment_aki_path': deploy_kernel, 'tftp_server': tftp_server, + 'ipxe_timeout': ipxe_timeout_in_ms, } 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, 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) def test__build_pxe_config_options_whole_disk_image(self, build_pxe_mock, @@ -303,6 +313,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase): 'tftp_server': tftp_server, 'aki_path': 'no_kernel', 'ari_path': 'no_ramdisk', + 'ipxe_timeout': 0, } image_info = {'deploy_kernel': ('deploy_kernel', @@ -345,7 +356,8 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase): 'pxe_append_params': 'my-pxe-append-params', 'tftp_server': 'my-tftp-server', 'aki_path': 'no_kernel', - 'ari_path': 'no_ramdisk'} + 'ari_path': 'no_ramdisk', + 'ipxe_timeout': 0} self.assertEqual(expected_options, options) @mock.patch.object(deploy_utils, 'fetch_images', autospec=True) diff --git a/releasenotes/notes/ipxe_timeout_parameter-03fc3c76c520fac2.yaml b/releasenotes/notes/ipxe_timeout_parameter-03fc3c76c520fac2.yaml new file mode 100644 index 0000000000..14a53c3c93 --- /dev/null +++ b/releasenotes/notes/ipxe_timeout_parameter-03fc3c76c520fac2.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add the ability to adjust ipxe timeout during image downloading, default is + still unlimited (0).