Merge "Add an option to create inspector-compatible boot.ipxe"

This commit is contained in:
Zuul 2021-11-26 10:55:50 +00:00 committed by Gerrit Code Review
commit ae2dc84cb4
7 changed files with 90 additions and 5 deletions

View File

@ -369,7 +369,8 @@ def create_ipxe_boot_script():
"""Render the iPXE boot script into the HTTP root directory""" """Render the iPXE boot script into the HTTP root directory"""
boot_script = utils.render_template( boot_script = utils.render_template(
CONF.pxe.ipxe_boot_script, CONF.pxe.ipxe_boot_script,
{'ipxe_for_mac_uri': PXE_CFG_DIR_NAME + '/'}) {'ipxe_for_mac_uri': PXE_CFG_DIR_NAME + '/',
'ipxe_fallback_script': CONF.pxe.ipxe_fallback_script})
bootfile_path = os.path.join( bootfile_path = os.path.join(
CONF.deploy.http_root, CONF.deploy.http_root,
os.path.basename(CONF.pxe.ipxe_boot_script)) os.path.basename(CONF.pxe.ipxe_boot_script))

View File

@ -144,6 +144,10 @@ opts = [
'$pybasedir', 'drivers/modules/boot.ipxe'), '$pybasedir', '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.StrOpt('ipxe_fallback_script',
help=_('File name (e.g. inspector.ipxe) of an iPXE script to '
'fall back to when booting to a MAC-specific script '
'fails. When not set, booting will fail in this case.')),
cfg.IntOpt('ipxe_timeout', cfg.IntOpt('ipxe_timeout',
default=0, default=0,
help=_('Timeout value (in seconds) for downloading an image ' help=_('Timeout value (in seconds) for downloading an image '

View File

@ -11,12 +11,22 @@ echo Attempting to boot from MAC ${net${netid}/mac:hexhyp}
chain {{ ipxe_for_mac_uri }}${net${netid}/mac:hexhyp} || goto loop chain {{ ipxe_for_mac_uri }}${net${netid}/mac:hexhyp} || goto loop
:loop_done :loop_done
{% if ipxe_fallback_script -%}
chain {{ ipxe_fallback_script }} | goto boot_failed
:boot_failed
{% endif -%}
echo PXE boot failed! No configuration found for any of the present NICs. echo PXE boot failed! No configuration found for any of the present NICs.
echo Press any key to reboot... echo Press any key to reboot...
prompt --timeout 180 prompt --timeout 180
reboot reboot
:old_rom :old_rom
{% if ipxe_fallback_script -%}
chain {{ ipxe_fallback_script }} | goto boot_failed_old_rom
:boot_failed_old_rom
{% endif -%}
echo PXE boot failed! No configuration found for NIC ${mac:hexhyp}. echo PXE boot failed! No configuration found for NIC ${mac:hexhyp}.
echo Please update your iPXE ROM and retry. echo Please update your iPXE ROM and retry.
echo Press any key to reboot... echo Press any key to reboot...

View File

@ -154,6 +154,17 @@ class TestPXEUtils(db_base.DbTestCase):
self.assertEqual(str(expected_template), rendered_template) self.assertEqual(str(expected_template), rendered_template)
def test_fallback_ipxe_boot_script(self):
rendered_template = utils.render_template(
CONF.pxe.ipxe_boot_script,
{'ipxe_for_mac_uri': 'pxelinux.cfg/',
'ipxe_fallback_script': 'inspector.ipxe'})
with open('ironic/tests/unit/drivers/boot-fallback.ipxe') as f:
expected_template = f.read().rstrip()
self.assertEqual(str(expected_template), rendered_template)
def test_default_ipxe_config(self): def test_default_ipxe_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.
@ -714,7 +725,8 @@ class TestPXEUtils(db_base.DbTestCase):
'foo') 'foo')
render_mock.assert_called_once_with( render_mock.assert_called_once_with(
CONF.pxe.ipxe_boot_script, CONF.pxe.ipxe_boot_script,
{'ipxe_for_mac_uri': 'pxelinux.cfg/'}) {'ipxe_for_mac_uri': 'pxelinux.cfg/',
'ipxe_fallback_script': None})
@mock.patch.object(os.path, 'isfile', lambda path: True) @mock.patch.object(os.path, 'isfile', lambda path: True)
@mock.patch('ironic.common.utils.file_has_content', autospec=True) @mock.patch('ironic.common.utils.file_has_content', autospec=True)
@ -735,7 +747,27 @@ class TestPXEUtils(db_base.DbTestCase):
'foo') 'foo')
render_mock.assert_called_once_with( render_mock.assert_called_once_with(
CONF.pxe.ipxe_boot_script, CONF.pxe.ipxe_boot_script,
{'ipxe_for_mac_uri': 'pxelinux.cfg/'}) {'ipxe_for_mac_uri': 'pxelinux.cfg/',
'ipxe_fallback_script': None})
@mock.patch.object(os.path, 'isfile', lambda path: False)
@mock.patch('ironic.common.utils.file_has_content', autospec=True)
@mock.patch('ironic.common.utils.write_to_file', autospec=True)
@mock.patch('ironic.common.utils.render_template', autospec=True)
def test_create_ipxe_boot_script_fallback(self, render_mock, write_mock,
file_has_content_mock):
self.config(ipxe_fallback_script='inspector.ipxe', group='pxe')
render_mock.return_value = 'foo'
pxe_utils.create_ipxe_boot_script()
self.assertFalse(file_has_content_mock.called)
write_mock.assert_called_once_with(
os.path.join(CONF.deploy.http_root,
os.path.basename(CONF.pxe.ipxe_boot_script)),
'foo')
render_mock.assert_called_once_with(
CONF.pxe.ipxe_boot_script,
{'ipxe_for_mac_uri': 'pxelinux.cfg/',
'ipxe_fallback_script': 'inspector.ipxe'})
@mock.patch.object(os.path, 'isfile', lambda path: True) @mock.patch.object(os.path, 'isfile', lambda path: True)
@mock.patch('ironic.common.utils.file_has_content', autospec=True) @mock.patch('ironic.common.utils.file_has_content', autospec=True)

View File

@ -0,0 +1,31 @@
#!ipxe
# NOTE(lucasagomes): Loop over all network devices and boot from
# the first one capable of booting. For more information see:
# https://bugs.launchpad.net/ironic/+bug/1504482
set netid:int32 -1
:loop
inc netid || chain pxelinux.cfg/${mac:hexhyp} || goto old_rom
isset ${net${netid}/mac} || goto loop_done
echo Attempting to boot from MAC ${net${netid}/mac:hexhyp}
chain pxelinux.cfg/${net${netid}/mac:hexhyp} || goto loop
:loop_done
chain inspector.ipxe | goto boot_failed
:boot_failed
echo PXE boot failed! No configuration found for any of the present NICs.
echo Press any key to reboot...
prompt --timeout 180
reboot
:old_rom
chain inspector.ipxe | goto boot_failed_old_rom
:boot_failed_old_rom
echo PXE boot failed! No configuration found for NIC ${mac:hexhyp}.
echo Please update your iPXE ROM and retry.
echo Press any key to reboot...
prompt --timeout 180
reboot

View File

@ -395,7 +395,8 @@ class iPXEBootTestCase(db_base.DbTestCase):
'foo') 'foo')
render_mock.assert_called_once_with( render_mock.assert_called_once_with(
CONF.pxe.ipxe_boot_script, CONF.pxe.ipxe_boot_script,
{'ipxe_for_mac_uri': 'pxelinux.cfg/'}) {'ipxe_for_mac_uri': 'pxelinux.cfg/',
'ipxe_fallback_script': None})
@mock.patch.object(os.path, 'isfile', lambda path: False) @mock.patch.object(os.path, 'isfile', lambda path: False)
@mock.patch('ironic.common.utils.file_has_content', autospec=True) @mock.patch('ironic.common.utils.file_has_content', autospec=True)
@ -415,7 +416,8 @@ class iPXEBootTestCase(db_base.DbTestCase):
'foo') 'foo')
render_mock.assert_called_once_with( render_mock.assert_called_once_with(
CONF.pxe.ipxe_boot_script, CONF.pxe.ipxe_boot_script,
{'ipxe_for_mac_uri': 'pxelinux.cfg/'}) {'ipxe_for_mac_uri': 'pxelinux.cfg/',
'ipxe_fallback_script': None})
@mock.patch.object(os.path, 'isfile', lambda path: True) @mock.patch.object(os.path, 'isfile', lambda path: True)
@mock.patch.object(common_utils, 'file_has_content', lambda *args: True) @mock.patch.object(common_utils, 'file_has_content', lambda *args: True)

View File

@ -0,0 +1,5 @@
---
features:
- |
Adds a new configuration option ``[pxe]ipxe_fallback_script`` which allows
iPXE boot to fall back to e.g. ironic-inspector iPXE script.