Merge "Fix ipv6 URL formatting for pxe/iPXE"

This commit is contained in:
Zuul
2018-11-21 07:47:05 +00:00
committed by Gerrit Code Review
3 changed files with 105 additions and 47 deletions

View File

@@ -21,6 +21,7 @@ from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import fileutils
from oslo_utils import importutils
from oslo_utils import netutils
from ironic.common import dhcp_factory
from ironic.common import exception
@@ -410,11 +411,12 @@ def _dhcp_option_file_or_url(task, urlboot=False):
if not urlboot:
return boot_file
elif urlboot:
path_prefix = get_tftp_path_prefix()
if path_prefix == '':
path_prefix = '/'
return ("tftp://" + CONF.pxe.tftp_server
+ path_prefix + boot_file)
if netutils.is_valid_ipv6(CONF.pxe.tftp_server):
host = "[%s]" % CONF.pxe.tftp_server
else:
host = CONF.pxe.tftp_server
return "tftp://{host}/{boot_file}".format(host=host,
boot_file=boot_file)
def dhcp_options_for_instance(task, ipxe_enabled=False, url_boot=False):
@@ -434,12 +436,17 @@ def dhcp_options_for_instance(task, ipxe_enabled=False, url_boot=False):
"""
dhcp_opts = []
ip_version = int(CONF.pxe.ip_version)
dhcp_provider_name = CONF.dhcp.dhcp_provider
if ip_version == 4:
boot_file_param = DHCP_BOOTFILE_NAME
else:
# NOTE(TheJulia): Booting with v6 means it is always
# a URL reply.
boot_file_param = DHCPV6_BOOTFILE_NAME
if dhcp_provider_name == 'neutron':
# dnsmasq requires ipv6 options be explicitly flagged. :(
boot_file_param = "option6:{}".format(DHCPV6_BOOTFILE_NAME)
else:
boot_file_param = DHCPV6_BOOTFILE_NAME
url_boot = True
# NOTE(TheJulia): The ip_version value config from the PXE config is
# guarded in the configuration, so there is no real sense in having
@@ -449,8 +456,11 @@ def dhcp_options_for_instance(task, ipxe_enabled=False, url_boot=False):
if ipxe_enabled:
script_name = os.path.basename(CONF.pxe.ipxe_boot_script)
# TODO(TheJulia): We should make this smarter to handle unwrapped v6
# addresses, since the format is http://[ff80::1]:80/boot.ipxe.
# As opposed to requiring configuraiton, we can eventually make this
# dynamic, and would need to do similar then.
ipxe_script_url = '/'.join([CONF.deploy.http_url, script_name])
dhcp_provider_name = CONF.dhcp.dhcp_provider
# if the request comes from dumb firmware send them the iPXE
# boot image.
if dhcp_provider_name == 'neutron':
@@ -458,7 +468,7 @@ def dhcp_options_for_instance(task, ipxe_enabled=False, url_boot=False):
# to neutron "dhcp-match=set:ipxe,175" and use below option
dhcp_opts.append({'opt_name': "tag:!ipxe,%s" % boot_file_param,
'opt_value': boot_file})
dhcp_opts.append({'opt_name': "tag:ipxe,%s" % DHCP_BOOTFILE_NAME,
dhcp_opts.append({'opt_name': "tag:ipxe,%s" % boot_file_param,
'opt_value': ipxe_script_url})
else:
# !175 == non-iPXE.

View File

@@ -716,7 +716,10 @@ class TestPXEUtils(db_base.DbTestCase):
def _dhcp_options_for_instance(self, ip_version=4):
self.config(ip_version=ip_version, group='pxe')
self.config(tftp_server='192.0.2.1', group='pxe')
if ip_version == 4:
self.config(tftp_server='192.0.2.1', group='pxe')
elif ip_version == 6:
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')
@@ -725,9 +728,8 @@ class TestPXEUtils(db_base.DbTestCase):
# options are not imported, although they may be supported
# 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://192.0.2.1/tftp-path'
'/fake-bootfile',
expected_info = [{'opt_name': 'option6:59',
'opt_value': 'tftp://[ff80::1]/fake-bootfile',
'ip_version': ip_version}]
elif ip_version == 4:
expected_info = [{'opt_name': '67',
@@ -754,6 +756,7 @@ class TestPXEUtils(db_base.DbTestCase):
self._dhcp_options_for_instance(ip_version=4)
def test_dhcp_options_for_instance_ipv6(self):
self.config(tftp_server='ff80::1', group='pxe')
self._dhcp_options_for_instance(ip_version=6)
def _test_get_kernel_ramdisk_info(self, expected_dir, mode='deploy'):
@@ -803,69 +806,106 @@ class TestPXEUtils(db_base.DbTestCase):
self.config(http_root=expected_dir, group='deploy')
self._test_get_kernel_ramdisk_info(expected_dir, mode='rescue')
def _dhcp_options_for_instance_ipxe(self, task, boot_file):
self.config(tftp_server='192.0.2.1', group='pxe')
def _dhcp_options_for_instance_ipxe(self, task, boot_file, ip_version=4):
self.config(ipxe_enabled=True, group='pxe')
self.config(http_url='http://192.0.3.2:1234', group='deploy')
self.config(ipxe_boot_script='/test/boot.ipxe', group='pxe')
self.config(tftp_root='/tftp-path/', group='pxe')
if ip_version == 4:
self.config(tftp_server='192.0.2.1', group='pxe')
self.config(http_url='http://192.0.3.2:1234', group='deploy')
self.config(ipxe_boot_script='/test/boot.ipxe', group='pxe')
elif ip_version == 6:
self.config(tftp_server='ff80::1', group='pxe')
self.config(http_url='http://[ff80::1]:1234', group='deploy')
self.config(dhcp_provider='isc', group='dhcp')
expected_boot_script_url = 'http://192.0.3.2:1234/boot.ipxe'
expected_info = [{'opt_name': '!175,67',
'opt_value': boot_file,
'ip_version': 4},
{'opt_name': '66',
'opt_value': '192.0.2.1',
'ip_version': 4},
{'opt_name': '150',
'opt_value': '192.0.2.1',
'ip_version': 4},
{'opt_name': '67',
'opt_value': expected_boot_script_url,
'ip_version': 4},
{'opt_name': 'server-ip-address',
'opt_value': '192.0.2.1',
'ip_version': 4}]
if ip_version == 6:
# NOTE(TheJulia): DHCPv6 RFCs seem to indicate that the prior
# options are not imported, although they may be supported
# by vendors. The apparent proper option is to return a
# 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',
'ip_version': ip_version},
{'opt_name': '59',
'opt_value': expected_boot_script_url,
'ip_version': ip_version}]
elif ip_version == 4:
expected_boot_script_url = 'http://192.0.3.2:1234/boot.ipxe'
expected_info = [{'opt_name': '!175,67',
'opt_value': boot_file,
'ip_version': ip_version},
{'opt_name': '66',
'opt_value': '192.0.2.1',
'ip_version': ip_version},
{'opt_name': '150',
'opt_value': '192.0.2.1',
'ip_version': ip_version},
{'opt_name': '67',
'opt_value': expected_boot_script_url,
'ip_version': ip_version},
{'opt_name': 'server-ip-address',
'opt_value': '192.0.2.1',
'ip_version': ip_version}]
self.assertItemsEqual(expected_info,
pxe_utils.dhcp_options_for_instance(
task, ipxe_enabled=True))
self.config(dhcp_provider='neutron', group='dhcp')
expected_boot_script_url = 'http://192.0.3.2:1234/boot.ipxe'
expected_info = [{'opt_name': 'tag:!ipxe,67',
'opt_value': boot_file,
'ip_version': 4},
{'opt_name': '66',
'opt_value': '192.0.2.1',
'ip_version': 4},
{'opt_name': '150',
'opt_value': '192.0.2.1',
'ip_version': 4},
{'opt_name': 'tag:ipxe,67',
'opt_value': expected_boot_script_url,
'ip_version': 4},
{'opt_name': 'server-ip-address',
'opt_value': '192.0.2.1',
'ip_version': 4}]
if ip_version == 6:
# Boot URL variable set from prior test of isc parameters.
expected_info = [{'opt_name': 'tag:!ipxe,option6:59',
'opt_value': 'tftp://[ff80::1]/fake-bootfile',
'ip_version': ip_version},
{'opt_name': 'tag:ipxe,option6:59',
'opt_value': expected_boot_script_url,
'ip_version': ip_version}]
elif ip_version == 4:
expected_info = [{'opt_name': 'tag:!ipxe,67',
'opt_value': boot_file,
'ip_version': ip_version},
{'opt_name': '66',
'opt_value': '192.0.2.1',
'ip_version': ip_version},
{'opt_name': '150',
'opt_value': '192.0.2.1',
'ip_version': ip_version},
{'opt_name': 'tag:ipxe,67',
'opt_value': expected_boot_script_url,
'ip_version': ip_version},
{'opt_name': 'server-ip-address',
'opt_value': '192.0.2.1',
'ip_version': ip_version}]
self.assertItemsEqual(expected_info,
pxe_utils.dhcp_options_for_instance(
task, ipxe_enabled=True))
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')
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')
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')
with task_manager.acquire(self.context, self.node.uuid) as task:
self._dhcp_options_for_instance_ipxe(task, boot_file, ip_version=6)
@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',

View File

@@ -0,0 +1,8 @@
---
fixes:
- |
Fixes the URL generation with TFTP URLs does not account for IPv6 addresses
needing to be wrapped in '[]' in order to be parsed.
- |
Fixes DHCP option parameter generation to correctly return the proper
values for IPv6 based booting when iPXE is in use.