Change PXE logic to always link macs with UEFI
Our logic for elilo and grub excluded the MAC address based links from being created, which are handy for stand-alone use cases. This patch changes the default to always write MAC address based links, and allows the interface to tolerate an exception being raised if the ``[dhcp]dhcp_provider`` is set to ``none``. This effectively fixes the issue where users that do not use neutron were unable to use UEFI. Story: #2002887 Task: #22848 Change-Id: Ib8b9b2e3281184cd4e8d9a1a33a012f93edf237c
This commit is contained in:
parent
40af9848ac
commit
b464a195bb
@ -19,6 +19,7 @@ import os
|
|||||||
from ironic_lib import utils as ironic_utils
|
from ironic_lib import utils as ironic_utils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import excutils
|
||||||
from oslo_utils import fileutils
|
from oslo_utils import fileutils
|
||||||
|
|
||||||
from ironic.common import dhcp_factory
|
from ironic.common import dhcp_factory
|
||||||
@ -90,7 +91,10 @@ def _link_mac_pxe_configs(task):
|
|||||||
pxe_config_file_path = get_pxe_config_file_path(task.node.uuid)
|
pxe_config_file_path = get_pxe_config_file_path(task.node.uuid)
|
||||||
for port in task.ports:
|
for port in task.ports:
|
||||||
client_id = port.extra.get('client-id')
|
client_id = port.extra.get('client-id')
|
||||||
|
# Syslinux, ipxe, depending on settings.
|
||||||
create_link(_get_pxe_mac_path(port.address, client_id=client_id))
|
create_link(_get_pxe_mac_path(port.address, client_id=client_id))
|
||||||
|
# Grub2 MAC address only
|
||||||
|
create_link(_get_pxe_grub_mac_path(port.address))
|
||||||
|
|
||||||
|
|
||||||
def _link_ip_address_pxe_configs(task, hex_form):
|
def _link_ip_address_pxe_configs(task, hex_form):
|
||||||
@ -121,6 +125,10 @@ def _link_ip_address_pxe_configs(task, hex_form):
|
|||||||
ip_address_path)
|
ip_address_path)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_pxe_grub_mac_path(mac):
|
||||||
|
return os.path.join(get_root_dir(), mac + '.conf')
|
||||||
|
|
||||||
|
|
||||||
def _get_pxe_mac_path(mac, delimiter='-', client_id=None):
|
def _get_pxe_mac_path(mac, delimiter='-', client_id=None):
|
||||||
"""Convert a MAC address into a PXE config file name.
|
"""Convert a MAC address into a PXE config file name.
|
||||||
|
|
||||||
@ -137,7 +145,6 @@ def _get_pxe_mac_path(mac, delimiter='-', client_id=None):
|
|||||||
if client_id:
|
if client_id:
|
||||||
hw_type = '20-'
|
hw_type = '20-'
|
||||||
mac_file_name = hw_type + mac_file_name
|
mac_file_name = hw_type + mac_file_name
|
||||||
|
|
||||||
return os.path.join(get_root_dir(), PXE_CFG_DIR_NAME, mac_file_name)
|
return os.path.join(get_root_dir(), PXE_CFG_DIR_NAME, mac_file_name)
|
||||||
|
|
||||||
|
|
||||||
@ -259,7 +266,21 @@ def create_pxe_config(task, pxe_options, template=None):
|
|||||||
utils.write_to_file(pxe_config_file_path, pxe_config)
|
utils.write_to_file(pxe_config_file_path, pxe_config)
|
||||||
|
|
||||||
if is_uefi_boot_mode and not CONF.pxe.ipxe_enabled:
|
if is_uefi_boot_mode and not CONF.pxe.ipxe_enabled:
|
||||||
_link_ip_address_pxe_configs(task, hex_form)
|
# Always write the mac addresses
|
||||||
|
_link_mac_pxe_configs(task)
|
||||||
|
try:
|
||||||
|
_link_ip_address_pxe_configs(task, hex_form)
|
||||||
|
# NOTE(TheJulia): The IP address support will fail if the
|
||||||
|
# dhcp_provider interface is set to none. This will result
|
||||||
|
# in the MAC addresses and DHCP files being written, and
|
||||||
|
# we can remove IP address creation for the grub use
|
||||||
|
# case, considering that will ease removal of elilo support.
|
||||||
|
except exception.FailedToGetIPaddressesOnPort as e:
|
||||||
|
if CONF.dhcp.dhcp_provider != 'none':
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
LOG.error('Unable to create boot config, IP address '
|
||||||
|
'was unable to be retrieved. %(error)s',
|
||||||
|
{'error': e})
|
||||||
else:
|
else:
|
||||||
_link_mac_pxe_configs(task)
|
_link_mac_pxe_configs(task)
|
||||||
|
|
||||||
@ -308,16 +329,21 @@ def clean_up_pxe_config(task):
|
|||||||
True)
|
True)
|
||||||
except exception.InvalidIPv4Address:
|
except exception.InvalidIPv4Address:
|
||||||
continue
|
continue
|
||||||
|
except exception.FailedToGetIPAddressOnPort:
|
||||||
|
continue
|
||||||
# Cleaning up config files created for grub2.
|
# Cleaning up config files created for grub2.
|
||||||
ironic_utils.unlink_without_raise(ip_address_path)
|
ironic_utils.unlink_without_raise(ip_address_path)
|
||||||
# Cleaning up config files created for elilo.
|
# Cleaning up config files created for elilo.
|
||||||
ironic_utils.unlink_without_raise(hex_ip_path)
|
ironic_utils.unlink_without_raise(hex_ip_path)
|
||||||
else:
|
|
||||||
for port in task.ports:
|
|
||||||
client_id = port.extra.get('client-id')
|
|
||||||
ironic_utils.unlink_without_raise(
|
|
||||||
_get_pxe_mac_path(port.address, client_id=client_id))
|
|
||||||
|
|
||||||
|
for port in task.ports:
|
||||||
|
client_id = port.extra.get('client-id')
|
||||||
|
# syslinux, ipxe, etc.
|
||||||
|
ironic_utils.unlink_without_raise(
|
||||||
|
_get_pxe_mac_path(port.address, client_id=client_id))
|
||||||
|
# Grub2 MAC address based confiuration
|
||||||
|
ironic_utils.unlink_without_raise(
|
||||||
|
_get_pxe_grub_mac_path(port.address))
|
||||||
utils.rmtree_without_raise(os.path.join(get_root_dir(),
|
utils.rmtree_without_raise(os.path.join(get_root_dir(),
|
||||||
task.node.uuid))
|
task.node.uuid))
|
||||||
|
|
||||||
|
@ -3,5 +3,5 @@ set timeout=5
|
|||||||
set hidden_timeout_quiet=false
|
set hidden_timeout_quiet=false
|
||||||
|
|
||||||
menuentry "master" {
|
menuentry "master" {
|
||||||
configfile /tftpboot/$net_default_ip.conf
|
configfile /tftpboot/$net_default_mac.conf
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ from oslo_config import cfg
|
|||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from ironic.common import exception
|
||||||
from ironic.common import pxe_utils
|
from ironic.common import pxe_utils
|
||||||
from ironic.common import utils
|
from ironic.common import utils
|
||||||
from ironic.conductor import task_manager
|
from ironic.conductor import task_manager
|
||||||
@ -255,12 +256,18 @@ class TestPXEUtils(db_base.DbTestCase):
|
|||||||
create_link_calls = [
|
create_link_calls = [
|
||||||
mock.call(u'../1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
|
mock.call(u'../1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
|
||||||
'/tftpboot/pxelinux.cfg/01-11-22-33-44-55-66'),
|
'/tftpboot/pxelinux.cfg/01-11-22-33-44-55-66'),
|
||||||
|
mock.call(u'1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
|
||||||
|
'/tftpboot/11:22:33:44:55:66.conf'),
|
||||||
mock.call(u'../1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
|
mock.call(u'../1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
|
||||||
'/tftpboot/pxelinux.cfg/01-11-22-33-44-55-67')
|
'/tftpboot/pxelinux.cfg/01-11-22-33-44-55-67'),
|
||||||
|
mock.call(u'1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
|
||||||
|
'/tftpboot/11:22:33:44:55:67.conf')
|
||||||
]
|
]
|
||||||
unlink_calls = [
|
unlink_calls = [
|
||||||
mock.call('/tftpboot/pxelinux.cfg/01-11-22-33-44-55-66'),
|
mock.call('/tftpboot/pxelinux.cfg/01-11-22-33-44-55-66'),
|
||||||
|
mock.call('/tftpboot/11:22:33:44:55:66.conf'),
|
||||||
mock.call('/tftpboot/pxelinux.cfg/01-11-22-33-44-55-67'),
|
mock.call('/tftpboot/pxelinux.cfg/01-11-22-33-44-55-67'),
|
||||||
|
mock.call('/tftpboot/11:22:33:44:55:67.conf')
|
||||||
]
|
]
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
task.ports = [port_1, port_2]
|
task.ports = [port_1, port_2]
|
||||||
@ -288,12 +295,18 @@ class TestPXEUtils(db_base.DbTestCase):
|
|||||||
create_link_calls = [
|
create_link_calls = [
|
||||||
mock.call(u'../1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
|
mock.call(u'../1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
|
||||||
'/tftpboot/pxelinux.cfg/20-11-22-33-44-55-66'),
|
'/tftpboot/pxelinux.cfg/20-11-22-33-44-55-66'),
|
||||||
|
mock.call(u'1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
|
||||||
|
'/tftpboot/11:22:33:44:55:66.conf'),
|
||||||
mock.call(u'../1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
|
mock.call(u'../1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
|
||||||
'/tftpboot/pxelinux.cfg/20-11-22-33-44-55-67')
|
'/tftpboot/pxelinux.cfg/20-11-22-33-44-55-67'),
|
||||||
|
mock.call(u'1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
|
||||||
|
'/tftpboot/11:22:33:44:55:67.conf')
|
||||||
]
|
]
|
||||||
unlink_calls = [
|
unlink_calls = [
|
||||||
mock.call('/tftpboot/pxelinux.cfg/20-11-22-33-44-55-66'),
|
mock.call('/tftpboot/pxelinux.cfg/20-11-22-33-44-55-66'),
|
||||||
|
mock.call('/tftpboot/11:22:33:44:55:66.conf'),
|
||||||
mock.call('/tftpboot/pxelinux.cfg/20-11-22-33-44-55-67'),
|
mock.call('/tftpboot/pxelinux.cfg/20-11-22-33-44-55-67'),
|
||||||
|
mock.call('/tftpboot/11:22:33:44:55:67.conf')
|
||||||
]
|
]
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
task.ports = [port_1, port_2]
|
task.ports = [port_1, port_2]
|
||||||
@ -315,12 +328,18 @@ class TestPXEUtils(db_base.DbTestCase):
|
|||||||
create_link_calls = [
|
create_link_calls = [
|
||||||
mock.call(u'../1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
|
mock.call(u'../1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
|
||||||
'/httpboot/pxelinux.cfg/11-22-33-44-55-66'),
|
'/httpboot/pxelinux.cfg/11-22-33-44-55-66'),
|
||||||
|
mock.call(u'1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
|
||||||
|
'/httpboot/11:22:33:44:55:66.conf'),
|
||||||
mock.call(u'../1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
|
mock.call(u'../1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
|
||||||
'/httpboot/pxelinux.cfg/11-22-33-44-55-67'),
|
'/httpboot/pxelinux.cfg/11-22-33-44-55-67'),
|
||||||
|
mock.call(u'1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
|
||||||
|
'/httpboot/11:22:33:44:55:67.conf')
|
||||||
]
|
]
|
||||||
unlink_calls = [
|
unlink_calls = [
|
||||||
mock.call('/httpboot/pxelinux.cfg/11-22-33-44-55-66'),
|
mock.call('/httpboot/pxelinux.cfg/11-22-33-44-55-66'),
|
||||||
|
mock.call('/httpboot/11:22:33:44:55:66.conf'),
|
||||||
mock.call('/httpboot/pxelinux.cfg/11-22-33-44-55-67'),
|
mock.call('/httpboot/pxelinux.cfg/11-22-33-44-55-67'),
|
||||||
|
mock.call('/httpboot/11:22:33:44:55:67.conf'),
|
||||||
]
|
]
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
task.ports = [port_1, port_2]
|
task.ports = [port_1, port_2]
|
||||||
@ -505,6 +524,46 @@ class TestPXEUtils(db_base.DbTestCase):
|
|||||||
write_mock.assert_called_with(pxe_cfg_file_path,
|
write_mock.assert_called_with(pxe_cfg_file_path,
|
||||||
render_mock.return_value)
|
render_mock.return_value)
|
||||||
|
|
||||||
|
@mock.patch.object(os, 'chmod', autospec=True)
|
||||||
|
@mock.patch('ironic.common.pxe_utils._link_mac_pxe_configs',
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch('ironic.common.pxe_utils._link_ip_address_pxe_configs',
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch('ironic.common.utils.write_to_file', autospec=True)
|
||||||
|
@mock.patch('ironic.common.utils.render_template', autospec=True)
|
||||||
|
@mock.patch('oslo_utils.fileutils.ensure_tree', autospec=True)
|
||||||
|
def test_create_pxe_config_uefi_mac_address(
|
||||||
|
self, ensure_tree_mock, render_mock,
|
||||||
|
write_mock, link_ip_configs_mock,
|
||||||
|
link_mac_pxe_configs_mock, chmod_mock):
|
||||||
|
# TODO(TheJulia): We should... like... fix the template to
|
||||||
|
# enable mac address usage.....
|
||||||
|
grub_tmplte = "ironic/drivers/modules/pxe_grub_config.template"
|
||||||
|
link_ip_configs_mock.side_efect = exception.FailedToGetIPAddressOnPort(
|
||||||
|
port_id='blah')
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
task.node.properties['capabilities'] = 'boot_mode:uefi'
|
||||||
|
pxe_utils.create_pxe_config(task, self.pxe_options,
|
||||||
|
grub_tmplte)
|
||||||
|
|
||||||
|
ensure_calls = [
|
||||||
|
mock.call(os.path.join(CONF.pxe.tftp_root, self.node.uuid)),
|
||||||
|
mock.call(os.path.join(CONF.pxe.tftp_root, 'pxelinux.cfg')),
|
||||||
|
]
|
||||||
|
ensure_tree_mock.assert_has_calls(ensure_calls)
|
||||||
|
chmod_mock.assert_not_called()
|
||||||
|
render_mock.assert_called_with(
|
||||||
|
grub_tmplte,
|
||||||
|
{'pxe_options': self.pxe_options,
|
||||||
|
'ROOT': '(( ROOT ))',
|
||||||
|
'DISK_IDENTIFIER': '(( DISK_IDENTIFIER ))'})
|
||||||
|
link_mac_pxe_configs_mock.assert_called_once_with(task)
|
||||||
|
link_ip_configs_mock.assert_called_once_with(task, False)
|
||||||
|
|
||||||
|
pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid)
|
||||||
|
write_mock.assert_called_with(pxe_cfg_file_path,
|
||||||
|
render_mock.return_value)
|
||||||
|
|
||||||
@mock.patch.object(os, 'chmod', autospec=True)
|
@mock.patch.object(os, 'chmod', autospec=True)
|
||||||
@mock.patch('ironic.common.pxe_utils._link_mac_pxe_configs', autospec=True)
|
@mock.patch('ironic.common.pxe_utils._link_mac_pxe_configs', autospec=True)
|
||||||
@mock.patch('ironic.common.utils.write_to_file', autospec=True)
|
@mock.patch('ironic.common.utils.write_to_file', autospec=True)
|
||||||
@ -547,8 +606,13 @@ class TestPXEUtils(db_base.DbTestCase):
|
|||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
pxe_utils.clean_up_pxe_config(task)
|
pxe_utils.clean_up_pxe_config(task)
|
||||||
|
|
||||||
unlink_mock.assert_called_once_with("/tftpboot/pxelinux.cfg/01-%s"
|
ensure_calls = [
|
||||||
% address.replace(':', '-'))
|
mock.call("/tftpboot/pxelinux.cfg/01-%s"
|
||||||
|
% address.replace(':', '-')),
|
||||||
|
mock.call("/tftpboot/%s.conf" % address)
|
||||||
|
]
|
||||||
|
|
||||||
|
unlink_mock.assert_has_calls(ensure_calls)
|
||||||
rmtree_mock.assert_called_once_with(
|
rmtree_mock.assert_called_once_with(
|
||||||
os.path.join(CONF.pxe.tftp_root, self.node.uuid))
|
os.path.join(CONF.pxe.tftp_root, self.node.uuid))
|
||||||
|
|
||||||
@ -801,6 +865,35 @@ class TestPXEUtils(db_base.DbTestCase):
|
|||||||
rmtree_mock.assert_called_once_with(
|
rmtree_mock.assert_called_once_with(
|
||||||
os.path.join(CONF.pxe.tftp_root, self.node.uuid))
|
os.path.join(CONF.pxe.tftp_root, self.node.uuid))
|
||||||
|
|
||||||
|
@mock.patch('ironic.common.utils.rmtree_without_raise', autospec=True)
|
||||||
|
@mock.patch('ironic_lib.utils.unlink_without_raise', autospec=True)
|
||||||
|
@mock.patch('ironic.common.dhcp_factory.DHCPFactory.provider',
|
||||||
|
autospec=True)
|
||||||
|
def test_clean_up_pxe_config_uefi_mac_address(
|
||||||
|
self, provider_mock, unlink_mock, rmtree_mock):
|
||||||
|
ip_address = '10.10.0.1'
|
||||||
|
address = "aa:aa:aa:aa:aa:aa"
|
||||||
|
properties = {'capabilities': 'boot_mode:uefi'}
|
||||||
|
object_utils.create_test_port(self.context, node_id=self.node.id,
|
||||||
|
address=address)
|
||||||
|
|
||||||
|
provider_mock.get_ip_addresses.return_value = [ip_address]
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
task.node.properties = properties
|
||||||
|
pxe_utils.clean_up_pxe_config(task)
|
||||||
|
|
||||||
|
unlink_calls = [
|
||||||
|
mock.call('/tftpboot/10.10.0.1.conf'),
|
||||||
|
mock.call('/tftpboot/0A0A0001.conf'),
|
||||||
|
mock.call('/tftpboot/pxelinux.cfg/01-%s' %
|
||||||
|
address.replace(':', '-'))
|
||||||
|
]
|
||||||
|
|
||||||
|
unlink_mock.assert_has_calls(unlink_calls)
|
||||||
|
rmtree_mock.assert_called_once_with(
|
||||||
|
os.path.join(CONF.pxe.tftp_root, self.node.uuid))
|
||||||
|
|
||||||
@mock.patch('ironic.common.utils.rmtree_without_raise', autospec=True)
|
@mock.patch('ironic.common.utils.rmtree_without_raise', autospec=True)
|
||||||
@mock.patch('ironic_lib.utils.unlink_without_raise', autospec=True)
|
@mock.patch('ironic_lib.utils.unlink_without_raise', autospec=True)
|
||||||
@mock.patch('ironic.common.dhcp_factory.DHCPFactory.provider',
|
@mock.patch('ironic.common.dhcp_factory.DHCPFactory.provider',
|
||||||
@ -840,8 +933,13 @@ class TestPXEUtils(db_base.DbTestCase):
|
|||||||
task.node.properties = properties
|
task.node.properties = properties
|
||||||
pxe_utils.clean_up_pxe_config(task)
|
pxe_utils.clean_up_pxe_config(task)
|
||||||
|
|
||||||
unlink_mock.assert_called_once_with(
|
ensure_calls = [
|
||||||
'/httpboot/pxelinux.cfg/aa-aa-aa-aa-aa-aa')
|
mock.call("/httpboot/pxelinux.cfg/%s"
|
||||||
|
% address.replace(':', '-')),
|
||||||
|
mock.call("/httpboot/%s.conf" % address)
|
||||||
|
]
|
||||||
|
|
||||||
|
unlink_mock.assert_has_calls(ensure_calls)
|
||||||
rmtree_mock.assert_called_once_with(
|
rmtree_mock.assert_called_once_with(
|
||||||
os.path.join(CONF.deploy.http_root, self.node.uuid))
|
os.path.join(CONF.deploy.http_root, self.node.uuid))
|
||||||
|
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
upgrade:
|
||||||
|
- |
|
||||||
|
Operators utilizing ``grub`` for PXE booting, typically with UEFI, should
|
||||||
|
change their deployed master PXE configuration file provided for nodes PXE
|
||||||
|
booting using grub. Ironic 11.1 now writes both MAC address and IP address
|
||||||
|
based PXE confiuration links for network booting via ``grub``.
|
||||||
|
The grub variable should be changed from ``$net_default_ip`` to
|
||||||
|
``$net_default_mac``. IP address support is deprecated and will be removed
|
||||||
|
in the Stein release.
|
||||||
|
deprecations:
|
||||||
|
- |
|
||||||
|
Support for ironic to link PXE boot configuration files via the assigned
|
||||||
|
interface IP address has been deprecated. This option was only the case
|
||||||
|
when ``[pxe]ipxe_enabled`` was set to ``false`` and the node was
|
||||||
|
being deployed using UEFI.
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
Fixes support for ``grub`` based UEFI PXE booting by enabling links to the
|
||||||
|
PXE configuration files to be written using the MAC address of the node in
|
||||||
|
addition to the interface IP address. If the ``[dhcp]dhcp_provider``
|
||||||
|
option is set to ``none``, only the MAC based links will be created.
|
Loading…
Reference in New Issue
Block a user