Merge "Fix DHCPv6 support"

This commit is contained in:
Zuul 2018-10-25 06:29:00 +00:00 committed by Gerrit Code Review
commit dd02698ad4
7 changed files with 150 additions and 59 deletions

View File

@ -43,9 +43,15 @@ PXE_CFG_DIR_NAME = CONF.pxe.pxe_config_subdir
DHCP_CLIENT_ID = '61' # rfc2132
DHCP_TFTP_SERVER_NAME = '66' # rfc2132
DHCP_BOOTFILE_NAME = '67' # rfc2132
DHCPV6_BOOTFILE_NAME = '59' # rfc5870
# NOTE(TheJulia): adding note for the bootfile parameter
# field as defined by RFC 5870. No practical examples seem
# available. Neither grub2 or ipxe seem to leverage this.
# DHCPV6_BOOTFILE_PARAMS = '60' # rfc5870
DHCP_TFTP_SERVER_ADDRESS = '150' # rfc5859
DHCP_IPXE_ENCAP_OPTS = '175' # Tentatively Assigned
DHCP_TFTP_PATH_PREFIX = '210' # rfc5071
DEPLOY_KERNEL_RAMDISK_LABELS = ['deploy_kernel', 'deploy_ramdisk']
RESCUE_KERNEL_RAMDISK_LABELS = ['rescue_kernel', 'rescue_ramdisk']
KERNEL_RAMDISK_LABELS = {'deploy': DEPLOY_KERNEL_RAMDISK_LABELS,
@ -391,16 +397,58 @@ def clean_up_pxe_config(task):
task.node.uuid))
def dhcp_options_for_instance(task):
def _dhcp_option_file_or_url(task, urlboot=False):
"""Returns the appropriate file or URL.
:param task: A TaskManager object.
:param url_boot: Boolean value default False to indicate if a
URL should be returned to the file as opposed
to a file.
"""
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:
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)
def dhcp_options_for_instance(task, ipxe_enabled=False, url_boot=False):
"""Retrieves the DHCP PXE boot options.
:param task: A TaskManager instance.
:param ipxe_enabled: Default false boolean that siganls if iPXE
formatting should be returned by the method
for DHCP server configuration.
:param url_boot: Default false boolean to inform the method if
a URL should be returned to boot the node.
If [pxe]ip_version is set to `6`, then this option
has no effect as url_boot form is required by DHCPv6
standards.
:returns: Dictionary to be sent to the networking service describing
the DHCP options to be set.
"""
dhcp_opts = []
ip_version = int(CONF.pxe.ip_version)
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
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
# anything else here in the event the value is something aside from
# 4 or 6, as there are no other possible values.
boot_file = _dhcp_option_file_or_url(task, url_boot)
boot_file = deploy_utils.get_pxe_boot_file(task.node)
if is_ipxe_enabled(task):
if ipxe_enabled:
script_name = os.path.basename(CONF.pxe.ipxe_boot_script)
ipxe_script_url = '/'.join([CONF.deploy.http_url, script_name])
dhcp_provider_name = CONF.dhcp.dhcp_provider
@ -409,7 +457,7 @@ def dhcp_options_for_instance(task):
if dhcp_provider_name == 'neutron':
# Neutron use dnsmasq as default DHCP agent, add extra config
# to neutron "dhcp-match=set:ipxe,175" and use below option
dhcp_opts.append({'opt_name': "tag:!ipxe,%s" % DHCP_BOOTFILE_NAME,
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,
'opt_value': ipxe_script_url})
@ -417,25 +465,27 @@ def dhcp_options_for_instance(task):
# !175 == non-iPXE.
# http://ipxe.org/howto/dhcpd#ipxe-specific_options
dhcp_opts.append({'opt_name': "!%s,%s" % (DHCP_IPXE_ENCAP_OPTS,
DHCP_BOOTFILE_NAME),
boot_file_param),
'opt_value': boot_file})
dhcp_opts.append({'opt_name': DHCP_BOOTFILE_NAME,
dhcp_opts.append({'opt_name': boot_file_param,
'opt_value': ipxe_script_url})
else:
dhcp_opts.append({'opt_name': DHCP_BOOTFILE_NAME,
dhcp_opts.append({'opt_name': boot_file_param,
'opt_value': boot_file})
# 210 == tftp server path-prefix or tftp root, will be used to find
# pxelinux.cfg directory. The pxelinux.0 loader infers this information
# from it's own path, but Petitboot needs it to be specified by this
# option since it doesn't use pxelinux.0 loader.
dhcp_opts.append({'opt_name': DHCP_TFTP_PATH_PREFIX,
'opt_value': get_tftp_path_prefix()})
dhcp_opts.append({'opt_name': DHCP_TFTP_SERVER_NAME,
'opt_value': CONF.pxe.tftp_server})
dhcp_opts.append({'opt_name': DHCP_TFTP_SERVER_ADDRESS,
'opt_value': CONF.pxe.tftp_server})
if not url_boot:
dhcp_opts.append(
{'opt_name': DHCP_TFTP_PATH_PREFIX,
'opt_value': get_tftp_path_prefix()})
if not url_boot:
dhcp_opts.append({'opt_name': DHCP_TFTP_SERVER_NAME,
'opt_value': CONF.pxe.tftp_server})
dhcp_opts.append({'opt_name': DHCP_TFTP_SERVER_ADDRESS,
'opt_value': CONF.pxe.tftp_server})
# NOTE(vsaienko) set this option specially for dnsmasq case as it always
# sets `siaddr` field which is treated by pxe clients as TFTP server
# see page 9 https://tools.ietf.org/html/rfc2131.
@ -449,12 +499,13 @@ def dhcp_options_for_instance(task):
# unknown options but potentially it may blow up with others.
# Related bug was opened on Neutron side:
# https://bugs.launchpad.net/neutron/+bug/1723354
dhcp_opts.append({'opt_name': 'server-ip-address',
'opt_value': CONF.pxe.tftp_server})
if not url_boot:
dhcp_opts.append({'opt_name': 'server-ip-address',
'opt_value': CONF.pxe.tftp_server})
# Append the IP version for all the configuration options
for opt in dhcp_opts:
opt.update({'ip_version': int(CONF.pxe.ip_version)})
opt.update({'ip_version': ip_version})
return dhcp_opts
@ -593,10 +644,10 @@ def get_image_info(node, mode='deploy'):
def build_deploy_pxe_options(task, pxe_info, mode='deploy'):
pxe_opts = {}
node = task.node
# TODO(TheJulia): In the future this should become an argument
ipxe_enabled = is_ipxe_enabled(task)
kernel_label = '%s_kernel' % mode
ramdisk_label = '%s_ramdisk' % mode
ipxe_enabled = is_ipxe_enabled(task)
for label, option in ((kernel_label, 'deployment_aki_path'),
(ramdisk_label, 'deployment_ari_path')):
if ipxe_enabled:
@ -839,7 +890,7 @@ def prepare_instance_pxe_config(task, image_info,
"""
node = task.node
dhcp_opts = dhcp_options_for_instance(task)
dhcp_opts = dhcp_options_for_instance(task, ipxe_enabled)
provider = dhcp_factory.DHCPFactory()
provider.update_dhcp(task, dhcp_opts)
pxe_config_path = get_pxe_config_file_path(

View File

@ -142,7 +142,8 @@ class iPXEBoot(pxe_base.PXEBaseMixin, base.BootInterface):
# or was deleted.
pxe_utils.create_ipxe_boot_script()
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
dhcp_opts = pxe_utils.dhcp_options_for_instance(
task, ipxe_enabled=True)
provider = dhcp_factory.DHCPFactory()
provider.update_dhcp(task, dhcp_opts)
@ -219,7 +220,8 @@ class iPXEBoot(pxe_base.PXEBaseMixin, base.BootInterface):
pxe_utils.cache_ramdisk_kernel(task, instance_image_info)
# If it's going to PXE boot we need to update the DHCP server
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
dhcp_opts = pxe_utils.dhcp_options_for_instance(task,
ipxe_enabled=True)
provider = dhcp_factory.DHCPFactory()
provider.update_dhcp(task, dhcp_opts)

View File

@ -141,8 +141,8 @@ class PXEBoot(pxe_base.PXEBaseMixin, base.BootInterface):
"""
node = task.node
mode = deploy_utils.rescue_or_deploy_mode(node)
if CONF.pxe.ipxe_enabled:
ipxe_enabled = CONF.pxe.ipxe_enabled
if ipxe_enabled:
# NOTE(mjturek): At this point, the ipxe boot script should
# already exist as it is created at startup time. However, we
# call the boot script create method here to assert its
@ -150,7 +150,8 @@ class PXEBoot(pxe_base.PXEBaseMixin, base.BootInterface):
# or was deleted.
pxe_utils.create_ipxe_boot_script()
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
dhcp_opts = pxe_utils.dhcp_options_for_instance(
task, ipxe_enabled=ipxe_enabled)
provider = dhcp_factory.DHCPFactory()
provider.update_dhcp(task, dhcp_opts)
@ -224,7 +225,8 @@ class PXEBoot(pxe_base.PXEBaseMixin, base.BootInterface):
pxe_utils.cache_ramdisk_kernel(task, instance_image_info)
# If it's going to PXE boot we need to update the DHCP server
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
dhcp_opts = pxe_utils.dhcp_options_for_instance(
task, ipxe_enabled)
provider = dhcp_factory.DHCPFactory()
provider.update_dhcp(task, dhcp_opts)

View File

@ -719,22 +719,33 @@ class TestPXEUtils(db_base.DbTestCase):
self.config(tftp_server='192.0.2.1', group='pxe')
self.config(pxe_bootfile_name='fake-bootfile', group='pxe')
self.config(tftp_root='/tftp-path/', group='pxe')
expected_info = [{'opt_name': '67',
'opt_value': 'fake-bootfile',
'ip_version': ip_version},
{'opt_name': '210',
'opt_value': '/tftp-path/',
'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': 'server-ip-address',
'opt_value': '192.0.2.1',
'ip_version': ip_version}
]
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_info = [{'opt_name': '59',
'opt_value': 'tftp://192.0.2.1/tftp-path'
'/fake-bootfile',
'ip_version': ip_version}]
elif ip_version == 4:
expected_info = [{'opt_name': '67',
'opt_value': 'fake-bootfile',
'ip_version': ip_version},
{'opt_name': '210',
'opt_value': '/tftp-path/',
'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': 'server-ip-address',
'opt_value': '192.0.2.1',
'ip_version': ip_version}
]
with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertEqual(expected_info,
pxe_utils.dhcp_options_for_instance(task))
@ -817,7 +828,8 @@ class TestPXEUtils(db_base.DbTestCase):
'ip_version': 4}]
self.assertItemsEqual(expected_info,
pxe_utils.dhcp_options_for_instance(task))
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'
@ -838,7 +850,8 @@ class TestPXEUtils(db_base.DbTestCase):
'ip_version': 4}]
self.assertItemsEqual(expected_info,
pxe_utils.dhcp_options_for_instance(task))
pxe_utils.dhcp_options_for_instance(
task, ipxe_enabled=True))
def test_dhcp_options_for_instance_ipxe_bios(self):
boot_file = 'fake-bootfile-bios'

View File

@ -257,7 +257,8 @@ class iPXEBootTestCase(db_base.DbTestCase):
'rescue_ramdisk': 'r'}
self.node.save()
with task_manager.acquire(self.context, self.node.uuid) as task:
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
dhcp_opts = pxe_utils.dhcp_options_for_instance(
task, ipxe_enabled=True)
task.driver.boot.prepare_ramdisk(task, {'foo': 'bar'})
mock_deploy_img_info.assert_called_once_with(task.node, mode=mode)
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
@ -538,7 +539,8 @@ class iPXEBootTestCase(db_base.DbTestCase):
'ramdisk': ('', '/path/to/ramdisk')}
get_image_info_mock.return_value = image_info
with task_manager.acquire(self.context, self.node.uuid) as task:
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
dhcp_opts = pxe_utils.dhcp_options_for_instance(
task, ipxe_enabled=True)
pxe_config_path = pxe_utils.get_pxe_config_file_path(
task.node.uuid)
task.node.properties['capabilities'] = 'boot_mode:bios'
@ -578,7 +580,8 @@ class iPXEBootTestCase(db_base.DbTestCase):
self.node.provision_state = states.ACTIVE
self.node.save()
with task_manager.acquire(self.context, self.node.uuid) as task:
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
dhcp_opts = pxe_utils.dhcp_options_for_instance(
task, ipxe_enabled=True)
pxe_config_path = pxe_utils.get_pxe_config_file_path(
task.node.uuid)
task.node.properties['capabilities'] = 'boot_mode:bios'
@ -615,7 +618,8 @@ class iPXEBootTestCase(db_base.DbTestCase):
'ramdisk': ('', '/path/to/ramdisk')}
get_image_info_mock.return_value = image_info
with task_manager.acquire(self.context, self.node.uuid) as task:
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
dhcp_opts = pxe_utils.dhcp_options_for_instance(
task, ipxe_enabled=True)
task.node.properties['capabilities'] = 'boot_mode:bios'
task.node.driver_internal_info['is_whole_disk_image'] = False
@ -644,7 +648,8 @@ class iPXEBootTestCase(db_base.DbTestCase):
dhcp_factory_mock.return_value = provider_mock
get_image_info_mock.return_value = {}
with task_manager.acquire(self.context, self.node.uuid) as task:
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
dhcp_opts = pxe_utils.dhcp_options_for_instance(
task, ipxe_enabled=True)
task.node.properties['capabilities'] = 'boot_mode:bios'
task.node.driver_internal_info['is_whole_disk_image'] = True
task.driver.boot.prepare_instance(task)
@ -688,7 +693,8 @@ class iPXEBootTestCase(db_base.DbTestCase):
with task_manager.acquire(self.context, self.node.uuid) as task:
task.node.driver_internal_info = {
'boot_from_volume': vol_id}
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
dhcp_opts = pxe_utils.dhcp_options_for_instance(task,
ipxe_enabled=True)
pxe_config_path = pxe_utils.get_pxe_config_file_path(
task.node.uuid)
task.node.properties['capabilities'] = 'boot_mode:bios'

View File

@ -255,7 +255,8 @@ class PXEBootTestCase(db_base.DbTestCase):
'rescue_ramdisk': 'r'}
self.node.save()
with task_manager.acquire(self.context, self.node.uuid) as task:
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
dhcp_opts = pxe_utils.dhcp_options_for_instance(
task, ipxe_enabled=CONF.pxe.ipxe_enabled)
task.driver.boot.prepare_ramdisk(task, {'foo': 'bar'})
mock_deploy_img_info.assert_called_once_with(task.node, mode=mode)
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
@ -543,7 +544,8 @@ class PXEBootTestCase(db_base.DbTestCase):
'ramdisk': ('', '/path/to/ramdisk')}
get_image_info_mock.return_value = image_info
with task_manager.acquire(self.context, self.node.uuid) as task:
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
dhcp_opts = pxe_utils.dhcp_options_for_instance(
task, ipxe_enabled=CONF.pxe.ipxe_enabled)
pxe_config_path = pxe_utils.get_pxe_config_file_path(
task.node.uuid)
task.node.properties['capabilities'] = 'boot_mode:bios'
@ -583,7 +585,8 @@ class PXEBootTestCase(db_base.DbTestCase):
self.node.provision_state = states.ACTIVE
self.node.save()
with task_manager.acquire(self.context, self.node.uuid) as task:
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
dhcp_opts = pxe_utils.dhcp_options_for_instance(
task, ipxe_enabled=CONF.pxe.ipxe_enabled)
pxe_config_path = pxe_utils.get_pxe_config_file_path(
task.node.uuid)
task.node.properties['capabilities'] = 'boot_mode:bios'
@ -621,7 +624,8 @@ class PXEBootTestCase(db_base.DbTestCase):
'ramdisk': ('', '/path/to/ramdisk')}
get_image_info_mock.return_value = image_info
with task_manager.acquire(self.context, self.node.uuid) as task:
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
dhcp_opts = pxe_utils.dhcp_options_for_instance(
task, ipxe_enabled=CONF.pxe.ipxe_enabled)
task.node.properties['capabilities'] = 'boot_mode:bios'
task.node.driver_internal_info['is_whole_disk_image'] = False
@ -647,7 +651,8 @@ class PXEBootTestCase(db_base.DbTestCase):
dhcp_factory_mock.return_value = provider_mock
get_image_info_mock.return_value = {}
with task_manager.acquire(self.context, self.node.uuid) as task:
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
dhcp_opts = pxe_utils.dhcp_options_for_instance(
task, CONF.pxe.ipxe_enabled)
task.node.properties['capabilities'] = 'boot_mode:bios'
task.node.driver_internal_info['is_whole_disk_image'] = True
task.driver.boot.prepare_instance(task)
@ -691,7 +696,8 @@ class PXEBootTestCase(db_base.DbTestCase):
with task_manager.acquire(self.context, self.node.uuid) as task:
task.node.driver_internal_info = {
'boot_from_volume': vol_id}
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
dhcp_opts = pxe_utils.dhcp_options_for_instance(task,
ipxe_enabled=True)
pxe_config_path = pxe_utils.get_pxe_config_file_path(
task.node.uuid)
task.node.properties['capabilities'] = 'boot_mode:bios'
@ -780,7 +786,8 @@ class PXEBootTestCase(db_base.DbTestCase):
instance_info['capabilities'] = {'boot_option': 'ramdisk'}
task.node.instance_info = instance_info
task.node.save()
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
dhcp_opts = pxe_utils.dhcp_options_for_instance(
task, ipxe_enabled=CONF.pxe.ipxe_enabled)
pxe_config_path = pxe_utils.get_pxe_config_file_path(
task.node.uuid)
task.driver.boot.prepare_instance(task)
@ -871,7 +878,8 @@ class PXERamdiskDeployTestCase(db_base.DbTestCase):
'ramdisk': ('', '/path/to/ramdisk')}
get_image_info_mock.return_value = image_info
with task_manager.acquire(self.context, self.node.uuid) as task:
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
dhcp_opts = pxe_utils.dhcp_options_for_instance(
task, ipxe_enabled=CONF.pxe.ipxe_enabled)
pxe_config_path = pxe_utils.get_pxe_config_file_path(
task.node.uuid)
task.node.properties['capabilities'] = 'boot_option:netboot'

View File

@ -0,0 +1,9 @@
---
fixes:
- |
Fixes a misunderstanding in how DHCPv6 booting of machines operates
in that only a URL to the boot loader is expected in that case, as opposed
to traditional TFTP parameters. Now a URL is sent to the client in the form
of ``tftp://<tftp_address>/<tftp_path>/<boot_file>``. See
`story 1744620 <https://storyboard.openstack.org/#!/story/1744620>`_
for more information.