Merge "Provide a path to set explicit ipxe bootloaders"

This commit is contained in:
Zuul 2020-07-08 12:08:48 +00:00 committed by Gerrit Code Review
commit 19866e3ddb
10 changed files with 227 additions and 53 deletions

View File

@ -1691,10 +1691,8 @@ function configure_ironic_conductor {
local pxebin
pxebin=`basename $IRONIC_PXE_BOOT_IMAGE`
uefipxebin=`basename $(get_uefi_ipxe_boot_file)`
iniset $IRONIC_CONF_FILE pxe pxe_config_template '$pybasedir/drivers/modules/ipxe_config.template'
iniset $IRONIC_CONF_FILE pxe pxe_bootfile_name $pxebin
iniset $IRONIC_CONF_FILE pxe uefi_pxe_config_template '$pybasedir/drivers/modules/ipxe_config.template'
iniset $IRONIC_CONF_FILE pxe uefi_pxe_bootfile_name $uefipxebin
iniset $IRONIC_CONF_FILE pxe ipxe_bootfile_name $pxebin
iniset $IRONIC_CONF_FILE pxe uefi_ipxe_bootfile_name $uefipxebin
iniset $IRONIC_CONF_FILE deploy http_root $IRONIC_HTTP_DIR
iniset $IRONIC_CONF_FILE deploy http_url "http://$([[ $IRONIC_HTTP_SERVER =~ : ]] && echo "[$IRONIC_HTTP_SERVER]" || echo $IRONIC_HTTP_SERVER):$IRONIC_HTTP_PORT"
if [[ "$IRONIC_IPXE_USE_SWIFT" == "True" ]]; then

View File

@ -357,41 +357,59 @@ on the Bare Metal service node(s) where ``ironic-conductor`` is running.
Ubuntu::
cp /usr/lib/ipxe/{undionly.kpxe,ipxe.efi} /tftpboot
cp /usr/lib/ipxe/{undionly.kpxe,ipxe.efi,snponly.efi} /tftpboot
Fedora/RHEL7/CentOS7::
cp /usr/share/ipxe/{undionly.kpxe,ipxe.efi} /tftpboot
cp /usr/share/ipxe/{undionly.kpxe,ipxe.efi,snponly.efi} /tftpboot
#. Enable/Configure iPXE in the Bare Metal Service's configuration file
(/etc/ironic/ironic.conf):
#. Enable/Configure iPXE overrides in the Bare Metal Service's configuration
file **if required** (/etc/ironic/ironic.conf):
.. code-block:: ini
[pxe]
# Enable iPXE boot. (boolean value)
ipxe_enabled=True
# Neutron bootfile DHCP parameter. (string value)
pxe_bootfile_name=undionly.kpxe
ipxe_bootfile_name=undionly.kpxe
# Bootfile DHCP parameter for UEFI boot mode. (string value)
uefi_pxe_bootfile_name=ipxe.efi
uefi_ipxe_bootfile_name=ipxe.efi
# Template file for PXE configuration. (string value)
pxe_config_template=$pybasedir/drivers/modules/ipxe_config.template
# Template file for PXE configuration for UEFI boot loader.
# (string value)
uefi_pxe_config_template=$pybasedir/drivers/modules/ipxe_config.template
ipxe_config_template=$pybasedir/drivers/modules/ipxe_config.template
.. note::
The ``[pxe]ipxe_enabled`` option has been deprecated and will be removed
in the T* development cycle. Users should instead consider use of the
``ipxe`` boot interface. The same default use of iPXE functionality can
be achieved by setting the ``[DEFAULT]default_boot_interface`` option
to ``ipxe``.
Most UEFI systems have integrated networking which means the
``[pxe]uefi_ipxe_bootfile_name`` setting should be set to
``snponly.efi``.
.. note::
Setting the iPXE parameters noted in the code block above to no value,
in other words setting a line to something like ``ipxe_bootfile_name=``
will result in ironic falling back to the default values of the non-iPXE
PXE settings. This is for backwards compatability.
#. Ensure iPXE is the default PXE, if applicable.
In earlier versions of ironic, a ``[pxe]ipxe_enabled`` setting allowing
operators to declare the behavior of the conductor to exclusively operate
as if only iPXE was to be used. As time moved on, iPXE functionality was
moved to it's own ``ipxe`` boot interface.
If you want to emulate that same hehavior, set the following in the
configuration file (/etc/ironic/ironic.conf):
.. code-block:: ini
[DEFAULT]
default_boot_interface=ipxe
enabled_boot_interfaces=ipxe,pxe
.. note::
The ``[DEFAULT]enabled_boot_interfaces`` setting may be exclusively set
to ``ipxe``, however ironic has multiple interfaces available depending
on the hardware types available for use.
#. It is possible to configure the Bare Metal service in such a way
that nodes will boot into the deploy image directly from Object Storage.
@ -442,7 +460,6 @@ on the Bare Metal service node(s) where ``ironic-conductor`` is running.
sudo service ironic-conductor restart
PXE multi-architecture setup
----------------------------
@ -498,6 +515,10 @@ nodes will be deployed by 'grubaa64.efi', and ppc64 nodes by 'bootppc64'::
commands, you'll need to switch to use ``linux`` and ``initrd`` command
instead.
.. note::
A ``[pxe]ipxe_bootfile_name_by_arch`` setting is available for multi-arch
iPXE based deployment, and defaults to the same behavior as the comperable
``[pxe]pxe_bootfile_by_arch`` setting for standard PXE.
PXE timeouts tuning
-------------------

View File

@ -265,7 +265,10 @@ def create_pxe_config(task, pxe_options, template=None, ipxe_enabled=False):
"""
LOG.debug("Building PXE config for node %s", task.node.uuid)
if template is None:
template = deploy_utils.get_pxe_config_template(task.node)
if ipxe_enabled:
template = deploy_utils.get_ipxe_config_template(task.node)
else:
template = deploy_utils.get_pxe_config_template(task.node)
_ensure_config_dirs_exist(task, ipxe_enabled)
@ -384,7 +387,16 @@ def _dhcp_option_file_or_url(task, urlboot=False, ip_version=None):
to return options for DHCP. Possible options
are 4, and 6.
"""
boot_file = deploy_utils.get_pxe_boot_file(task.node)
try:
if task.driver.boot.ipxe_enabled:
boot_file = deploy_utils.get_ipxe_boot_file(task.node)
else:
boot_file = deploy_utils.get_pxe_boot_file(task.node)
except AttributeError:
# Support boot interfaces that lack an explicit ipxe_enabled
# attribute flag.
boot_file = deploy_utils.get_pxe_boot_file(task.node)
# NOTE(TheJulia): There are additional cases as we add new
# features, so the logic below is in the form of if/elif/elif
if not urlboot:
@ -800,7 +812,10 @@ def build_service_pxe_config(task, instance_image_info,
pxe_options = build_pxe_config_options(task, instance_image_info,
service=True,
ipxe_enabled=ipxe_enabled)
pxe_config_template = deploy_utils.get_pxe_config_template(node)
if ipxe_enabled:
pxe_config_template = deploy_utils.get_ipxe_config_template(node)
else:
pxe_config_template = deploy_utils.get_pxe_config_template(node)
create_pxe_config(task, pxe_options, pxe_config_template,
ipxe_enabled=ipxe_enabled)
@ -942,8 +957,12 @@ def prepare_instance_pxe_config(task, image_info,
pxe_options = build_pxe_config_options(
task, image_info, service=ramdisk_boot,
ipxe_enabled=ipxe_enabled)
pxe_config_template = (
deploy_utils.get_pxe_config_template(node))
if ipxe_enabled:
pxe_config_template = (
deploy_utils.get_ipxe_config_template(node))
else:
pxe_config_template = (
deploy_utils.get_pxe_config_template(node))
create_pxe_config(
task, pxe_options, pxe_config_template,
ipxe_enabled=ipxe_enabled)

View File

@ -54,14 +54,21 @@ opts = [
'$pybasedir', 'drivers/modules/pxe_config.template'),
mutable=True,
help=_('On ironic-conductor node, template file for PXE '
'configuration.')),
'loader configuration.')),
cfg.StrOpt('ipxe_config_template',
default=os.path.join(
'$pybasedir', 'drivers/modules/ipxe_config.template'),
mutable=True,
help=_('On ironic-conductor node, template file for iPXE '
'operations.')),
cfg.StrOpt('uefi_pxe_config_template',
default=os.path.join(
'$pybasedir',
'drivers/modules/pxe_grub_config.template'),
mutable=True,
help=_('On ironic-conductor node, template file for PXE '
'configuration for UEFI boot loader.')),
'configuration for UEFI boot loader. Generally this '
'is used for GRUB specific templates.')),
cfg.DictOpt('pxe_config_template_by_arch',
default={},
mutable=True,
@ -107,10 +114,22 @@ opts = [
cfg.StrOpt('uefi_pxe_bootfile_name',
default='bootx64.efi',
help=_('Bootfile DHCP parameter for UEFI boot mode.')),
cfg.StrOpt('ipxe_bootfile_name',
default='undionly.kpxe',
help=_('Bootfile DHCP parameter.')),
cfg.StrOpt('uefi_ipxe_bootfile_name',
default='ipxe.efi',
help=_('Bootfile DHCP parameter for UEFI boot mode. If you '
'experience problems with booting using it, try '
'snponly.efi.')),
cfg.DictOpt('pxe_bootfile_name_by_arch',
default={},
help=_('Bootfile DHCP parameter per node architecture. '
'For example: aarch64:grubaa64.efi')),
cfg.DictOpt('ipxe_bootfile_name_by_arch',
default={},
help=_('Bootfile DHCP parameter per node architecture. '
'For example: aarch64:ipxe_aa64.efi')),
cfg.StrOpt('ipxe_boot_script',
default=os.path.join(
'$pybasedir', 'drivers/modules/boot.ipxe'),

View File

@ -378,6 +378,54 @@ def get_pxe_boot_file(node):
return boot_file
def get_ipxe_boot_file(node):
"""Return the iPXE boot file name requested for deploy.
This method returns iPXE boot file name to be used for deploy.
Architecture specific boot file is searched first. BIOS/UEFI
boot file is used if no valid architecture specific file found.
If no valid value is found, the default reverts to the
``get_pxe_boot_file`` method and thus the
``[pxe]pxe_bootfile_name`` and ``[pxe]uefi_ipxe_bootfile_name``
settings.
:param node: A single Node.
:returns: The iPXE boot file name.
"""
cpu_arch = node.properties.get('cpu_arch')
boot_file = CONF.pxe.ipxe_bootfile_name_by_arch.get(cpu_arch)
if boot_file is None:
if boot_mode_utils.get_boot_mode(node) == 'uefi':
boot_file = CONF.pxe.uefi_ipxe_bootfile_name
else:
boot_file = CONF.pxe.ipxe_bootfile_name
if boot_file is None:
boot_file = get_pxe_boot_file(node)
return boot_file
def get_ipxe_config_template(node):
"""Return the iPXE config template file name requested of deploy.
This method returns the iPXE configuration template file.
:param node: A single Node.
:returns: The iPXE config template file name.
"""
# NOTE(TheJulia): iPXE configuration files don't change based upon the
# architecture and we're not trying to support multiple different boot
# loaders by architecture as they are all consistent. Where as PXE
# could need to be grub for one arch, PXELINUX for another.
configured_template = CONF.pxe.ipxe_config_template
override_template = node.driver_info.get('pxe_template')
if override_template:
configured_template = override_template
return configured_template or get_pxe_config_template(node)
def get_pxe_config_template(node):
"""Return the PXE config template file name requested for deploy.

View File

@ -200,7 +200,10 @@ class PXEBaseMixin(object):
if ramdisk_params.get("ipa-api-url"):
pxe_options["ipa-api-url"] = ramdisk_params["ipa-api-url"]
pxe_config_template = deploy_utils.get_pxe_config_template(node)
if self.ipxe_enabled:
pxe_config_template = deploy_utils.get_ipxe_config_template(node)
else:
pxe_config_template = deploy_utils.get_pxe_config_template(node)
pxe_utils.create_pxe_config(task, pxe_options,
pxe_config_template,

View File

@ -645,7 +645,7 @@ class TestPXEUtils(db_base.DbTestCase):
'config'),
pxe_utils.get_pxe_config_file_path(self.node.uuid))
def _dhcp_options_for_instance(self, ip_version=4):
def _dhcp_options_for_instance(self, ip_version=4, ipxe=False):
self.config(ip_version=ip_version, group='pxe')
if ip_version == 4:
self.config(tftp_server='192.0.2.1', group='pxe')
@ -653,6 +653,10 @@ class TestPXEUtils(db_base.DbTestCase):
self.config(tftp_server='ff80::1', group='pxe')
self.config(pxe_bootfile_name='fake-bootfile', group='pxe')
self.config(tftp_root='/tftp-path/', group='pxe')
if ipxe:
bootfile = 'fake-bootfile-ipxe'
else:
bootfile = 'fake-bootfile'
if ip_version == 6:
# NOTE(TheJulia): DHCPv6 RFCs seem to indicate that the prior
@ -660,11 +664,11 @@ class TestPXEUtils(db_base.DbTestCase):
# by vendors. The apparent proper option is to return a
# URL in the field https://tools.ietf.org/html/rfc5970#section-3
expected_info = [{'opt_name': '59',
'opt_value': 'tftp://[ff80::1]/fake-bootfile',
'opt_value': 'tftp://[ff80::1]/%s' % bootfile,
'ip_version': ip_version}]
elif ip_version == 4:
expected_info = [{'opt_name': '67',
'opt_value': 'fake-bootfile',
'opt_value': bootfile,
'ip_version': ip_version},
{'opt_name': '210',
'opt_value': '/tftp-path/',
@ -1320,7 +1324,7 @@ class iPXEBuildConfigOptionsTestCase(db_base.DbTestCase):
# URL in the field https://tools.ietf.org/html/rfc5970#section-3
expected_boot_script_url = 'http://[ff80::1]:1234/boot.ipxe'
expected_info = [{'opt_name': '!175,59',
'opt_value': 'tftp://[ff80::1]/fake-bootfile',
'opt_value': 'tftp://[ff80::1]/%s' % boot_file,
'ip_version': ip_version},
{'opt_name': '59',
'opt_value': expected_boot_script_url,
@ -1352,7 +1356,7 @@ class iPXEBuildConfigOptionsTestCase(db_base.DbTestCase):
if ip_version == 6:
# Boot URL variable set from prior test of isc parameters.
expected_info = [{'opt_name': 'tag:!ipxe6,59',
'opt_value': 'tftp://[ff80::1]/fake-bootfile',
'opt_value': 'tftp://[ff80::1]/%s' % boot_file,
'ip_version': ip_version},
{'opt_name': 'tag:ipxe6,59',
'opt_value': expected_boot_script_url,
@ -1381,23 +1385,23 @@ class iPXEBuildConfigOptionsTestCase(db_base.DbTestCase):
def test_dhcp_options_for_instance_ipxe_bios(self):
self.config(ip_version=4, group='pxe')
boot_file = 'fake-bootfile-bios'
self.config(pxe_bootfile_name=boot_file, group='pxe')
boot_file = 'fake-bootfile-bios-ipxe'
self.config(ipxe_bootfile_name=boot_file, group='pxe')
with task_manager.acquire(self.context, self.node.uuid) as task:
self._dhcp_options_for_instance_ipxe(task, boot_file)
def test_dhcp_options_for_instance_ipxe_uefi(self):
self.config(ip_version=4, group='pxe')
boot_file = 'fake-bootfile-uefi'
self.config(uefi_pxe_bootfile_name=boot_file, group='pxe')
boot_file = 'fake-bootfile-uefi-ipxe'
self.config(uefi_ipxe_bootfile_name=boot_file, group='pxe')
with task_manager.acquire(self.context, self.node.uuid) as task:
task.node.properties['capabilities'] = 'boot_mode:uefi'
self._dhcp_options_for_instance_ipxe(task, boot_file)
def test_dhcp_options_for_ipxe_ipv6(self):
self.config(ip_version=6, group='pxe')
boot_file = 'fake-bootfile'
self.config(pxe_bootfile_name=boot_file, group='pxe')
boot_file = 'fake-bootfile-ipxe'
self.config(ipxe_bootfile_name=boot_file, group='pxe')
with task_manager.acquire(self.context, self.node.uuid) as task:
self._dhcp_options_for_instance_ipxe(task, boot_file, ip_version=6)

View File

@ -582,6 +582,34 @@ class GetPxeBootConfigTestCase(db_base.DbTestCase):
result = utils.get_pxe_boot_file(self.node)
self.assertEqual('bios-bootfile', result)
def test_get_ipxe_boot_file(self):
self.config(ipxe_bootfile_name='meow', group='pxe')
result = utils.get_ipxe_boot_file(self.node)
self.assertEqual('meow', result)
def test_get_ipxe_boot_file_uefi(self):
self.config(uefi_ipxe_bootfile_name='ipxe-uefi-bootfile', group='pxe')
properties = {'capabilities': 'boot_mode:uefi'}
self.node.properties = properties
result = utils.get_ipxe_boot_file(self.node)
self.assertEqual('ipxe-uefi-bootfile', result)
def test_get_ipxe_boot_file_other_arch(self):
arch_names = {'aarch64': 'ipxe-aa64.efi',
'x86_64': 'ipxe.kpxe'}
self.config(ipxe_bootfile_name_by_arch=arch_names, group='pxe')
properties = {'cpu_arch': 'aarch64', 'capabilities': 'boot_mode:uefi'}
self.node.properties = properties
result = utils.get_ipxe_boot_file(self.node)
self.assertEqual('ipxe-aa64.efi', result)
def test_get_ipxe_boot_file_fallback(self):
self.config(ipxe_bootfile_name=None, group='pxe')
self.config(uefi_ipxe_bootfile_name=None, group='pxe')
self.config(pxe_bootfile_name='lolcat', group='pxe')
result = utils.get_ipxe_boot_file(self.node)
self.assertEqual('lolcat', result)
def test_get_pxe_config_template_emtpy_property(self):
self.node.properties = {}
self.config(pxe_config_template_by_arch=self.template_by_arch,
@ -597,6 +625,28 @@ class GetPxeBootConfigTestCase(db_base.DbTestCase):
result = utils.get_pxe_config_template(node)
self.assertEqual('fake-template', result)
def test_get_ipxe_config_template(self):
node = obj_utils.create_test_node(
self.context, driver='fake-hardware')
self.assertIn('ipxe_config.template',
utils.get_ipxe_config_template(node))
def test_get_ipxe_config_template_none(self):
self.config(ipxe_config_template=None, group='pxe')
self.config(pxe_config_template='magical_bootloader',
group='pxe')
node = obj_utils.create_test_node(
self.context, driver='fake-hardware')
self.assertEqual('magical_bootloader',
utils.get_ipxe_config_template(node))
def test_get_ipxe_config_template_override_pxe_fallback(self):
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
driver_info={'pxe_template': 'magical'})
self.assertEqual('magical',
utils.get_ipxe_config_template(node))
@mock.patch('time.sleep', lambda sec: None)
class OtherFunctionTestCase(db_base.DbTestCase):

View File

@ -309,14 +309,9 @@ class iPXEBootTestCase(db_base.DbTestCase):
mock_cache_r_k.assert_called_once_with(
task, {'rescue_kernel': 'a', 'rescue_ramdisk': 'r'},
ipxe_enabled=True)
if uefi:
mock_pxe_config.assert_called_once_with(
task, {}, CONF.pxe.uefi_pxe_config_template,
ipxe_enabled=True)
else:
mock_pxe_config.assert_called_once_with(
task, {}, CONF.pxe.pxe_config_template,
ipxe_enabled=True)
mock_pxe_config.assert_called_once_with(
task, {}, CONF.pxe.ipxe_config_template,
ipxe_enabled=True)
def test_prepare_ramdisk(self):
self.node.provision_state = states.DEPLOYING
@ -699,7 +694,7 @@ class iPXEBootTestCase(db_base.DbTestCase):
ipxe_enabled=True)
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
create_pxe_config_mock.assert_called_once_with(
task, mock.ANY, CONF.pxe.pxe_config_template,
task, mock.ANY, CONF.pxe.ipxe_config_template,
ipxe_enabled=True)
switch_pxe_config_mock.assert_called_once_with(
pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50",
@ -816,7 +811,7 @@ class iPXEBootTestCase(db_base.DbTestCase):
self.assertFalse(cache_mock.called)
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
create_pxe_config_mock.assert_called_once_with(
task, mock.ANY, CONF.pxe.pxe_config_template,
task, mock.ANY, CONF.pxe.ipxe_config_template,
ipxe_enabled=True)
switch_pxe_config_mock.assert_called_once_with(
pxe_config_path, None, boot_modes.LEGACY_BIOS, False,

View File

@ -0,0 +1,17 @@
---
upgrade:
- |
Operators upgrading from earlier versions using PXE should explicitly set
``[pxe]ipxe_bootfile_name``, ``[pxe]uefi_ipxe_bootfile_name``, and
possibly ``[pxe]ipxe_bootfile_name_by_arch`` settings, as well as a
iPXE specific ``[pxe]ipxe_config_template`` override, if required.
Setting the ``[pxe]ipxe_config_template`` to no value will result in the
``[pxe]pxe_config_template`` being used. The default value points to the
supplied standard iPXE template, so only highly customized operators may
have to tune this setting.
fixes:
- |
Addresses the lack of an ability to explicitly set different bootloaders
for ``iPXE`` and ``PXE`` based boot operations via their respective
``ipxe`` and ``pxe`` boot interfaces.