Merge "Add an option to create inspector-compatible boot.ipxe"
This commit is contained in:
commit
ae2dc84cb4
@ -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))
|
||||||
|
@ -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 '
|
||||||
|
@ -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...
|
||||||
|
@ -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)
|
||||||
|
31
ironic/tests/unit/drivers/boot-fallback.ipxe
Normal file
31
ironic/tests/unit/drivers/boot-fallback.ipxe
Normal 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
|
||||||
|
|
@ -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)
|
||||||
|
5
releasenotes/notes/ipxe-fallback-a10c8ce422caa429.yaml
Normal file
5
releasenotes/notes/ipxe-fallback-a10c8ce422caa429.yaml
Normal 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.
|
Loading…
Reference in New Issue
Block a user