"dual stack" support for PXE/iPXE
Adds functionality for dual stack capabilities and automatic population to neutron with the correct response based upon the IP version of the provisioning/cleaning/rescue or tenant ports. This was origianlly intended to be separated from removing the need for [pxe]ip_version, however the resulting code changes from doing both this and making ironic support dual stacks touched the same tests and some of the same code, so combined is simpler. Change-Id: If7a296001e204ae0c9a49495731052ab33379628
This commit is contained in:
parent
1dee25f554
commit
cf412bc81e
@ -373,13 +373,16 @@ def clean_up_pxe_config(task, ipxe_enabled=False):
|
|||||||
task.node.uuid))
|
task.node.uuid))
|
||||||
|
|
||||||
|
|
||||||
def _dhcp_option_file_or_url(task, urlboot=False):
|
def _dhcp_option_file_or_url(task, urlboot=False, ip_version=None):
|
||||||
"""Returns the appropriate file or URL.
|
"""Returns the appropriate file or URL.
|
||||||
|
|
||||||
:param task: A TaskManager object.
|
:param task: A TaskManager object.
|
||||||
:param url_boot: Boolean value default False to indicate if a
|
:param url_boot: Boolean value default False to indicate if a
|
||||||
URL should be returned to the file as opposed
|
URL should be returned to the file as opposed
|
||||||
to a file.
|
to a file.
|
||||||
|
:param ip_version: Integer representing the version of IP of
|
||||||
|
to return options for DHCP. Possible options
|
||||||
|
are 4, and 6.
|
||||||
"""
|
"""
|
||||||
boot_file = deploy_utils.get_pxe_boot_file(task.node)
|
boot_file = deploy_utils.get_pxe_boot_file(task.node)
|
||||||
# NOTE(TheJulia): There are additional cases as we add new
|
# NOTE(TheJulia): There are additional cases as we add new
|
||||||
@ -387,12 +390,16 @@ def _dhcp_option_file_or_url(task, urlboot=False):
|
|||||||
if not urlboot:
|
if not urlboot:
|
||||||
return boot_file
|
return boot_file
|
||||||
elif urlboot:
|
elif urlboot:
|
||||||
host = utils.wrap_ipv6(CONF.pxe.tftp_server)
|
if CONF.my_ipv6 and ip_version == 6:
|
||||||
|
host = utils.wrap_ipv6(CONF.my_ipv6)
|
||||||
|
else:
|
||||||
|
host = utils.wrap_ipv6(CONF.pxe.tftp_server)
|
||||||
return "tftp://{host}/{boot_file}".format(host=host,
|
return "tftp://{host}/{boot_file}".format(host=host,
|
||||||
boot_file=boot_file)
|
boot_file=boot_file)
|
||||||
|
|
||||||
|
|
||||||
def dhcp_options_for_instance(task, ipxe_enabled=False, url_boot=False):
|
def dhcp_options_for_instance(task, ipxe_enabled=False, url_boot=False,
|
||||||
|
ip_version=None):
|
||||||
"""Retrieves the DHCP PXE boot options.
|
"""Retrieves the DHCP PXE boot options.
|
||||||
|
|
||||||
:param task: A TaskManager instance.
|
:param task: A TaskManager instance.
|
||||||
@ -404,13 +411,19 @@ def dhcp_options_for_instance(task, ipxe_enabled=False, url_boot=False):
|
|||||||
If [pxe]ip_version is set to `6`, then this option
|
If [pxe]ip_version is set to `6`, then this option
|
||||||
has no effect as url_boot form is required by DHCPv6
|
has no effect as url_boot form is required by DHCPv6
|
||||||
standards.
|
standards.
|
||||||
|
:param ip_version: The IP version of options to return as values
|
||||||
|
differ by IP version. Default to [pxe]ip_version.
|
||||||
|
Possible options are integers 4 or 6.
|
||||||
:returns: Dictionary to be sent to the networking service describing
|
:returns: Dictionary to be sent to the networking service describing
|
||||||
the DHCP options to be set.
|
the DHCP options to be set.
|
||||||
"""
|
"""
|
||||||
|
if ip_version:
|
||||||
|
use_ip_version = ip_version
|
||||||
|
else:
|
||||||
|
use_ip_version = int(CONF.pxe.ip_version)
|
||||||
dhcp_opts = []
|
dhcp_opts = []
|
||||||
ip_version = int(CONF.pxe.ip_version)
|
|
||||||
dhcp_provider_name = CONF.dhcp.dhcp_provider
|
dhcp_provider_name = CONF.dhcp.dhcp_provider
|
||||||
if ip_version == 4:
|
if use_ip_version == 4:
|
||||||
boot_file_param = DHCP_BOOTFILE_NAME
|
boot_file_param = DHCP_BOOTFILE_NAME
|
||||||
else:
|
else:
|
||||||
# NOTE(TheJulia): Booting with v6 means it is always
|
# NOTE(TheJulia): Booting with v6 means it is always
|
||||||
@ -421,7 +434,7 @@ def dhcp_options_for_instance(task, ipxe_enabled=False, url_boot=False):
|
|||||||
# guarded in the configuration, so there is no real sense in having
|
# guarded in the configuration, so there is no real sense in having
|
||||||
# anything else here in the event the value is something aside from
|
# anything else here in the event the value is something aside from
|
||||||
# 4 or 6, as there are no other possible values.
|
# 4 or 6, as there are no other possible values.
|
||||||
boot_file = _dhcp_option_file_or_url(task, url_boot)
|
boot_file = _dhcp_option_file_or_url(task, url_boot, use_ip_version)
|
||||||
|
|
||||||
if ipxe_enabled:
|
if ipxe_enabled:
|
||||||
# TODO(TheJulia): DHCPv6 through dnsmasq + ipxe matching simply
|
# TODO(TheJulia): DHCPv6 through dnsmasq + ipxe matching simply
|
||||||
@ -444,7 +457,7 @@ def dhcp_options_for_instance(task, ipxe_enabled=False, url_boot=False):
|
|||||||
# added in the Stein cycle which identifies the iPXE User-Class
|
# added in the Stein cycle which identifies the iPXE User-Class
|
||||||
# directly and is only sent in DHCPv6.
|
# directly and is only sent in DHCPv6.
|
||||||
|
|
||||||
if ip_version != 6:
|
if use_ip_version != 6:
|
||||||
dhcp_opts.append(
|
dhcp_opts.append(
|
||||||
{'opt_name': "tag:!ipxe,%s" % boot_file_param,
|
{'opt_name': "tag:!ipxe,%s" % boot_file_param,
|
||||||
'opt_value': boot_file}
|
'opt_value': boot_file}
|
||||||
@ -463,7 +476,7 @@ def dhcp_options_for_instance(task, ipxe_enabled=False, url_boot=False):
|
|||||||
else:
|
else:
|
||||||
# !175 == non-iPXE.
|
# !175 == non-iPXE.
|
||||||
# http://ipxe.org/howto/dhcpd#ipxe-specific_options
|
# http://ipxe.org/howto/dhcpd#ipxe-specific_options
|
||||||
if ip_version == 6:
|
if use_ip_version == 6:
|
||||||
LOG.warning('IPv6 is enabled and the DHCP driver appears set '
|
LOG.warning('IPv6 is enabled and the DHCP driver appears set '
|
||||||
'to a plugin aside from "neutron". Node %(name)s '
|
'to a plugin aside from "neutron". Node %(name)s '
|
||||||
'may not receive proper DHCPv6 provided '
|
'may not receive proper DHCPv6 provided '
|
||||||
@ -512,7 +525,7 @@ def dhcp_options_for_instance(task, ipxe_enabled=False, url_boot=False):
|
|||||||
|
|
||||||
# Append the IP version for all the configuration options
|
# Append the IP version for all the configuration options
|
||||||
for opt in dhcp_opts:
|
for opt in dhcp_opts:
|
||||||
opt.update({'ip_version': ip_version})
|
opt.update({'ip_version': use_ip_version})
|
||||||
|
|
||||||
return dhcp_opts
|
return dhcp_opts
|
||||||
|
|
||||||
@ -906,7 +919,16 @@ def prepare_instance_pxe_config(task, image_info,
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
node = task.node
|
node = task.node
|
||||||
dhcp_opts = dhcp_options_for_instance(task, ipxe_enabled)
|
# Generate options for both IPv4 and IPv6, and they can be
|
||||||
|
# filtered down later based upon the port options.
|
||||||
|
# TODO(TheJulia): This should be re-tooled during the Victoria
|
||||||
|
# development cycle so that we call a single method and return
|
||||||
|
# combined options. The method we currently call is relied upon
|
||||||
|
# by two eternal projects, to changing the behavior is not ideal.
|
||||||
|
dhcp_opts = dhcp_options_for_instance(task, ipxe_enabled,
|
||||||
|
ip_version=4)
|
||||||
|
dhcp_opts += dhcp_options_for_instance(task, ipxe_enabled,
|
||||||
|
ip_version=6)
|
||||||
provider = dhcp_factory.DHCPFactory()
|
provider = dhcp_factory.DHCPFactory()
|
||||||
provider.update_dhcp(task, dhcp_opts)
|
provider.update_dhcp(task, dhcp_opts)
|
||||||
pxe_config_path = get_pxe_config_file_path(
|
pxe_config_path = get_pxe_config_file_path(
|
||||||
|
@ -239,9 +239,20 @@ netconf_opts = [
|
|||||||
cfg.StrOpt('my_ip',
|
cfg.StrOpt('my_ip',
|
||||||
default=netutils.get_my_ipv4(),
|
default=netutils.get_my_ipv4(),
|
||||||
sample_default='127.0.0.1',
|
sample_default='127.0.0.1',
|
||||||
help=_('IP address of this host. If unset, will determine the '
|
help=_('IPv4 address of this host. If unset, will determine '
|
||||||
'IP programmatically. If unable to do so, will use '
|
'the IP programmatically. If unable to do so, will use '
|
||||||
'"127.0.0.1".')),
|
'"127.0.0.1". NOTE: This field does accept an IPv6 '
|
||||||
|
'address as an override for templates and URLs, '
|
||||||
|
'however it is recommended that [DEFAULT]my_ipv6 '
|
||||||
|
'is used along with DNS names for service URLs for '
|
||||||
|
'dual-stack environments.')),
|
||||||
|
cfg.StrOpt('my_ipv6',
|
||||||
|
default=None,
|
||||||
|
sample_default='2001:db8::1',
|
||||||
|
help=_('IP address of this host using IPv6. This value must '
|
||||||
|
'be supplied via the configuration and cannot be '
|
||||||
|
'adequately programmatically determined like the '
|
||||||
|
'[DEFAULT]my_ip parameter for IPv4.')),
|
||||||
]
|
]
|
||||||
|
|
||||||
notification_opts = [
|
notification_opts = [
|
||||||
|
@ -39,9 +39,11 @@ class BaseDHCP(object, metaclass=abc.ABCMeta):
|
|||||||
::
|
::
|
||||||
|
|
||||||
[{'opt_name': '67',
|
[{'opt_name': '67',
|
||||||
'opt_value': 'pxelinux.0'},
|
'opt_value': 'pxelinux.0',
|
||||||
|
'ip_version': 4},
|
||||||
{'opt_name': '66',
|
{'opt_name': '66',
|
||||||
'opt_value': '123.123.123.456'}]
|
'opt_value': '123.123.123.456',
|
||||||
|
'ip_version': 4}]
|
||||||
:param token: An optional authentication token. Deprecated, use context
|
:param token: An optional authentication token. Deprecated, use context
|
||||||
:param context: request context
|
:param context: request context
|
||||||
:type context: ironic.common.context.RequestContext
|
:type context: ironic.common.context.RequestContext
|
||||||
@ -63,9 +65,11 @@ class BaseDHCP(object, metaclass=abc.ABCMeta):
|
|||||||
::
|
::
|
||||||
|
|
||||||
[{'opt_name': '67',
|
[{'opt_name': '67',
|
||||||
'opt_value': 'pxelinux.0'},
|
'opt_value': 'pxelinux.0',
|
||||||
|
'ip_version': 4},
|
||||||
{'opt_name': '66',
|
{'opt_name': '66',
|
||||||
'opt_value': '123.123.123.456'}]
|
'opt_value': '123.123.123.456',
|
||||||
|
'ip_version': 4}]
|
||||||
|
|
||||||
:param vifs: A dict with keys 'ports' and 'portgroups' and
|
:param vifs: A dict with keys 'ports' and 'portgroups' and
|
||||||
dicts as values. Each dict has key/value pairs of the form
|
dicts as values. Each dict has key/value pairs of the form
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import ipaddress
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from neutronclient.common import exceptions as neutron_client_exc
|
from neutronclient.common import exceptions as neutron_client_exc
|
||||||
@ -49,9 +50,11 @@ class NeutronDHCPApi(base.BaseDHCP):
|
|||||||
::
|
::
|
||||||
|
|
||||||
[{'opt_name': '67',
|
[{'opt_name': '67',
|
||||||
'opt_value': 'pxelinux.0'},
|
'opt_value': 'pxelinux.0',
|
||||||
|
'ip_version': 4},
|
||||||
{'opt_name': '66',
|
{'opt_name': '66',
|
||||||
'opt_value': '123.123.123.456'}]
|
'opt_value': '123.123.123.456'},
|
||||||
|
'ip_version': 4}]
|
||||||
:param token: optional auth token. Deprecated, use context.
|
:param token: optional auth token. Deprecated, use context.
|
||||||
:param context: request context
|
:param context: request context
|
||||||
:type context: ironic.common.context.RequestContext
|
:type context: ironic.common.context.RequestContext
|
||||||
@ -59,8 +62,36 @@ class NeutronDHCPApi(base.BaseDHCP):
|
|||||||
"""
|
"""
|
||||||
super(NeutronDHCPApi, self).update_port_dhcp_opts(
|
super(NeutronDHCPApi, self).update_port_dhcp_opts(
|
||||||
port_id, dhcp_options, token=token, context=context)
|
port_id, dhcp_options, token=token, context=context)
|
||||||
port_req_body = {'port': {'extra_dhcp_opts': dhcp_options}}
|
|
||||||
try:
|
try:
|
||||||
|
neutron_client = neutron.get_client(token=token,
|
||||||
|
context=context)
|
||||||
|
|
||||||
|
fip = None
|
||||||
|
port = neutron_client.show_port(port_id).get('port')
|
||||||
|
try:
|
||||||
|
if port:
|
||||||
|
# TODO(TheJulia): We need to retool this down the
|
||||||
|
# road so that we handle ports and allow preferences
|
||||||
|
# for multi-address ports with different IP versions
|
||||||
|
# and enable operators to possibly select preferences
|
||||||
|
# for provisionioning operations.
|
||||||
|
# This is compounded by v6 mainly only being available
|
||||||
|
# with UEFI machines, so the support matrix also gets
|
||||||
|
# a little "weird".
|
||||||
|
# Ideally, we should work on this in Victoria.
|
||||||
|
fip = port.get('fixed_ips')[0]
|
||||||
|
except (TypeError, IndexError):
|
||||||
|
fip = None
|
||||||
|
update_opts = []
|
||||||
|
if fip:
|
||||||
|
ip_version = ipaddress.ip_address(fip['ip_address']).version
|
||||||
|
for option in dhcp_options:
|
||||||
|
if option.get('ip_version', 4) == ip_version:
|
||||||
|
update_opts.append(option)
|
||||||
|
else:
|
||||||
|
LOG.error('Requested to update port for port %s, '
|
||||||
|
'however port lacks an IP address.', port_id)
|
||||||
|
port_req_body = {'port': {'extra_dhcp_opts': update_opts}}
|
||||||
neutron.update_neutron_port(context, port_id, port_req_body)
|
neutron.update_neutron_port(context, port_id, port_req_body)
|
||||||
except neutron_client_exc.NeutronClientException:
|
except neutron_client_exc.NeutronClientException:
|
||||||
LOG.exception("Failed to update Neutron port %s.", port_id)
|
LOG.exception("Failed to update Neutron port %s.", port_id)
|
||||||
@ -75,9 +106,11 @@ class NeutronDHCPApi(base.BaseDHCP):
|
|||||||
::
|
::
|
||||||
|
|
||||||
[{'opt_name': '67',
|
[{'opt_name': '67',
|
||||||
'opt_value': 'pxelinux.0'},
|
'opt_value': 'pxelinux.0',
|
||||||
|
'ip_version': 4},
|
||||||
{'opt_name': '66',
|
{'opt_name': '66',
|
||||||
'opt_value': '123.123.123.456'}]
|
'opt_value': '123.123.123.456',
|
||||||
|
'ip_version': 4}]
|
||||||
:param vifs: a dict of Neutron port/portgroup dicts
|
:param vifs: a dict of Neutron port/portgroup dicts
|
||||||
to update DHCP options on. The port/portgroup dict
|
to update DHCP options on. The port/portgroup dict
|
||||||
key should be Ironic port UUIDs, and the values
|
key should be Ironic port UUIDs, and the values
|
||||||
|
@ -169,8 +169,16 @@ class PXEBaseMixin(object):
|
|||||||
# or was deleted.
|
# or was deleted.
|
||||||
pxe_utils.create_ipxe_boot_script()
|
pxe_utils.create_ipxe_boot_script()
|
||||||
|
|
||||||
|
# Generate options for both IPv4 and IPv6, and they can be
|
||||||
|
# filtered down later based upon the port options.
|
||||||
|
# TODO(TheJulia): This should be re-tooled during the Victoria
|
||||||
|
# development cycle so that we call a single method and return
|
||||||
|
# combined options. The method we currently call is relied upon
|
||||||
|
# by two eternal projects, to changing the behavior is not ideal.
|
||||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
||||||
task, ipxe_enabled=self.ipxe_enabled)
|
task, ipxe_enabled=self.ipxe_enabled, ip_version=4)
|
||||||
|
dhcp_opts += pxe_utils.dhcp_options_for_instance(
|
||||||
|
task, ipxe_enabled=self.ipxe_enabled, ip_version=6)
|
||||||
provider = dhcp_factory.DHCPFactory()
|
provider = dhcp_factory.DHCPFactory()
|
||||||
provider.update_dhcp(task, dhcp_opts)
|
provider.update_dhcp(task, dhcp_opts)
|
||||||
|
|
||||||
@ -259,7 +267,9 @@ class PXEBaseMixin(object):
|
|||||||
|
|
||||||
# If it's going to PXE boot we need to update the DHCP server
|
# If it's going to PXE boot we need to update the DHCP server
|
||||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
||||||
task, ipxe_enabled=self.ipxe_enabled)
|
task, ipxe_enabled=self.ipxe_enabled, ip_version=4)
|
||||||
|
dhcp_opts += pxe_utils.dhcp_options_for_instance(
|
||||||
|
task, ipxe_enabled=self.ipxe_enabled, ip_version=6)
|
||||||
provider = dhcp_factory.DHCPFactory()
|
provider = dhcp_factory.DHCPFactory()
|
||||||
provider.update_dhcp(task, dhcp_opts)
|
provider.update_dhcp(task, dhcp_opts)
|
||||||
|
|
||||||
|
@ -48,8 +48,9 @@ class TestNeutron(db_base.DbTestCase):
|
|||||||
|
|
||||||
dhcp_factory.DHCPFactory._dhcp_provider = None
|
dhcp_factory.DHCPFactory._dhcp_provider = None
|
||||||
|
|
||||||
|
@mock.patch('ironic.common.neutron.get_client', autospec=True)
|
||||||
@mock.patch('ironic.common.neutron.update_neutron_port', autospec=True)
|
@mock.patch('ironic.common.neutron.update_neutron_port', autospec=True)
|
||||||
def test_update_port_dhcp_opts(self, update_mock):
|
def test_update_port_dhcp_opts(self, update_mock, client_mock):
|
||||||
opts = [{'opt_name': 'bootfile-name',
|
opts = [{'opt_name': 'bootfile-name',
|
||||||
'opt_value': 'pxelinux.0'},
|
'opt_value': 'pxelinux.0'},
|
||||||
{'opt_name': 'tftp-server',
|
{'opt_name': 'tftp-server',
|
||||||
@ -58,6 +59,56 @@ class TestNeutron(db_base.DbTestCase):
|
|||||||
'opt_value': '1.1.1.1'}]
|
'opt_value': '1.1.1.1'}]
|
||||||
port_id = 'fake-port-id'
|
port_id = 'fake-port-id'
|
||||||
expected = {'port': {'extra_dhcp_opts': opts}}
|
expected = {'port': {'extra_dhcp_opts': opts}}
|
||||||
|
port_data = {
|
||||||
|
"id": port_id,
|
||||||
|
"fixed_ips": [
|
||||||
|
{
|
||||||
|
"ip_address": "192.168.1.3",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
client_mock.return_value.show_port.return_value = {'port': port_data}
|
||||||
|
|
||||||
|
api = dhcp_factory.DHCPFactory()
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
api.provider.update_port_dhcp_opts(port_id, opts,
|
||||||
|
context=task.context)
|
||||||
|
update_mock.assert_called_once_with(
|
||||||
|
self.context, port_id, expected)
|
||||||
|
|
||||||
|
@mock.patch('ironic.common.neutron.get_client', autospec=True)
|
||||||
|
@mock.patch('ironic.common.neutron.update_neutron_port', autospec=True)
|
||||||
|
def test_update_port_dhcp_opts_v6(self, update_mock, client_mock):
|
||||||
|
opts = [{'opt_name': 'bootfile-name',
|
||||||
|
'opt_value': 'pxelinux.0',
|
||||||
|
'ip_version': 4},
|
||||||
|
{'opt_name': 'tftp-server',
|
||||||
|
'opt_value': '1.1.1.1',
|
||||||
|
'ip_version': 4},
|
||||||
|
{'opt_name': 'server-ip-address',
|
||||||
|
'opt_value': '1.1.1.1',
|
||||||
|
'ip_version': 4},
|
||||||
|
{'opt_name': 'bootfile-url',
|
||||||
|
'opt_value': 'tftp://::1/file.name',
|
||||||
|
'ip_version': 6}]
|
||||||
|
port_id = 'fake-port-id'
|
||||||
|
expected = {
|
||||||
|
'port': {
|
||||||
|
'extra_dhcp_opts': [{
|
||||||
|
'opt_name': 'bootfile-url',
|
||||||
|
'opt_value': 'tftp://::1/file.name',
|
||||||
|
'ip_version': 6}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
port_data = {
|
||||||
|
"id": port_id,
|
||||||
|
"fixed_ips": [
|
||||||
|
{
|
||||||
|
"ip_address": "2001:db8::201",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
client_mock.return_value.show_port.return_value = {'port': port_data}
|
||||||
|
|
||||||
api = dhcp_factory.DHCPFactory()
|
api = dhcp_factory.DHCPFactory()
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
@ -66,10 +117,21 @@ class TestNeutron(db_base.DbTestCase):
|
|||||||
update_mock.assert_called_once_with(
|
update_mock.assert_called_once_with(
|
||||||
task.context, port_id, expected)
|
task.context, port_id, expected)
|
||||||
|
|
||||||
|
@mock.patch('ironic.common.neutron.get_client', autospec=True)
|
||||||
@mock.patch('ironic.common.neutron.update_neutron_port', autospec=True)
|
@mock.patch('ironic.common.neutron.update_neutron_port', autospec=True)
|
||||||
def test_update_port_dhcp_opts_with_exception(self, update_mock):
|
def test_update_port_dhcp_opts_with_exception(self, update_mock,
|
||||||
|
client_mock):
|
||||||
opts = [{}]
|
opts = [{}]
|
||||||
port_id = 'fake-port-id'
|
port_id = 'fake-port-id'
|
||||||
|
port_data = {
|
||||||
|
"id": port_id,
|
||||||
|
"fixed_ips": [
|
||||||
|
{
|
||||||
|
"ip_address": "192.168.1.3",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
client_mock.return_value.show_port.return_value = {'port': port_data}
|
||||||
update_mock.side_effect = (
|
update_mock.side_effect = (
|
||||||
neutron_client_exc.NeutronClientException())
|
neutron_client_exc.NeutronClientException())
|
||||||
|
|
||||||
|
@ -270,11 +270,14 @@ class iPXEBootTestCase(db_base.DbTestCase):
|
|||||||
self.node.save()
|
self.node.save()
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
||||||
task, ipxe_enabled=True)
|
task, ipxe_enabled=True, ip_version=4)
|
||||||
|
dhcp_opts += pxe_utils.dhcp_options_for_instance(
|
||||||
|
task, ipxe_enabled=True, ip_version=6)
|
||||||
task.driver.boot.prepare_ramdisk(task, {'foo': 'bar'})
|
task.driver.boot.prepare_ramdisk(task, {'foo': 'bar'})
|
||||||
mock_deploy_img_info.assert_called_once_with(task.node, mode=mode,
|
mock_deploy_img_info.assert_called_once_with(task.node, mode=mode,
|
||||||
ipxe_enabled=True)
|
ipxe_enabled=True)
|
||||||
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
provider_mock.update_dhcp.assert_called_once_with(
|
||||||
|
task, dhcp_opts)
|
||||||
if self.node.provision_state == states.DEPLOYING:
|
if self.node.provision_state == states.DEPLOYING:
|
||||||
get_boot_mode_mock.assert_called_once_with(task)
|
get_boot_mode_mock.assert_called_once_with(task)
|
||||||
set_boot_device_mock.assert_called_once_with(task,
|
set_boot_device_mock.assert_called_once_with(task,
|
||||||
@ -630,6 +633,8 @@ class iPXEBootTestCase(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:
|
||||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
||||||
task, ipxe_enabled=True)
|
task, ipxe_enabled=True)
|
||||||
|
dhcp_opts += pxe_utils.dhcp_options_for_instance(
|
||||||
|
task, ipxe_enabled=True, ip_version=6)
|
||||||
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
||||||
task.node.uuid, ipxe_enabled=True)
|
task.node.uuid, ipxe_enabled=True)
|
||||||
task.node.properties['capabilities'] = 'boot_mode:bios'
|
task.node.properties['capabilities'] = 'boot_mode:bios'
|
||||||
@ -672,6 +677,8 @@ class iPXEBootTestCase(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:
|
||||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
||||||
task, ipxe_enabled=True)
|
task, ipxe_enabled=True)
|
||||||
|
dhcp_opts += pxe_utils.dhcp_options_for_instance(
|
||||||
|
task, ipxe_enabled=True, ip_version=6)
|
||||||
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
||||||
task.node.uuid, ipxe_enabled=True)
|
task.node.uuid, ipxe_enabled=True)
|
||||||
task.node.properties['capabilities'] = 'boot_mode:bios'
|
task.node.properties['capabilities'] = 'boot_mode:bios'
|
||||||
@ -710,7 +717,9 @@ class iPXEBootTestCase(db_base.DbTestCase):
|
|||||||
get_image_info_mock.return_value = image_info
|
get_image_info_mock.return_value = image_info
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
||||||
task, ipxe_enabled=True)
|
task, ipxe_enabled=True, ip_version=4)
|
||||||
|
dhcp_opts += pxe_utils.dhcp_options_for_instance(
|
||||||
|
task, ipxe_enabled=True, ip_version=6)
|
||||||
task.node.properties['capabilities'] = 'boot_mode:bios'
|
task.node.properties['capabilities'] = 'boot_mode:bios'
|
||||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||||
|
|
||||||
@ -742,6 +751,8 @@ class iPXEBootTestCase(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:
|
||||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
||||||
task, ipxe_enabled=True)
|
task, ipxe_enabled=True)
|
||||||
|
dhcp_opts += pxe_utils.dhcp_options_for_instance(
|
||||||
|
task, ipxe_enabled=True, ip_version=6)
|
||||||
task.node.properties['capabilities'] = 'boot_mode:bios'
|
task.node.properties['capabilities'] = 'boot_mode:bios'
|
||||||
task.node.driver_internal_info['is_whole_disk_image'] = True
|
task.node.driver_internal_info['is_whole_disk_image'] = True
|
||||||
task.driver.boot.prepare_instance(task)
|
task.driver.boot.prepare_instance(task)
|
||||||
@ -786,6 +797,8 @@ class iPXEBootTestCase(db_base.DbTestCase):
|
|||||||
'boot_from_volume': vol_id}
|
'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)
|
ipxe_enabled=True)
|
||||||
|
dhcp_opts += pxe_utils.dhcp_options_for_instance(
|
||||||
|
task, ipxe_enabled=True, ip_version=6)
|
||||||
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
||||||
task.node.uuid, ipxe_enabled=True)
|
task.node.uuid, ipxe_enabled=True)
|
||||||
task.node.properties['capabilities'] = 'boot_mode:bios'
|
task.node.properties['capabilities'] = 'boot_mode:bios'
|
||||||
|
@ -86,6 +86,7 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
self.port = obj_utils.create_test_port(self.context,
|
self.port = obj_utils.create_test_port(self.context,
|
||||||
node_id=self.node.id)
|
node_id=self.node.id)
|
||||||
self.config(group='conductor', api_url='http://127.0.0.1:1234/')
|
self.config(group='conductor', api_url='http://127.0.0.1:1234/')
|
||||||
|
self.config(my_ipv6='2001:db8::1')
|
||||||
|
|
||||||
def test_get_properties(self):
|
def test_get_properties(self):
|
||||||
expected = pxe_base.COMMON_PROPERTIES
|
expected = pxe_base.COMMON_PROPERTIES
|
||||||
@ -267,6 +268,8 @@ class PXEBootTestCase(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:
|
||||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
||||||
task, ipxe_enabled=False)
|
task, ipxe_enabled=False)
|
||||||
|
dhcp_opts += pxe_utils.dhcp_options_for_instance(
|
||||||
|
task, ipxe_enabled=False, ip_version=6)
|
||||||
task.driver.boot.prepare_ramdisk(task, {'foo': 'bar'})
|
task.driver.boot.prepare_ramdisk(task, {'foo': 'bar'})
|
||||||
mock_deploy_img_info.assert_called_once_with(task.node,
|
mock_deploy_img_info.assert_called_once_with(task.node,
|
||||||
mode=mode,
|
mode=mode,
|
||||||
@ -552,7 +555,9 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
get_image_info_mock.return_value = image_info
|
get_image_info_mock.return_value = image_info
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
||||||
task, ipxe_enabled=False)
|
task, ipxe_enabled=False, ip_version=4)
|
||||||
|
dhcp_opts += pxe_utils.dhcp_options_for_instance(
|
||||||
|
task, ipxe_enabled=False, ip_version=6)
|
||||||
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
||||||
task.node.uuid)
|
task.node.uuid)
|
||||||
task.node.properties['capabilities'] = 'boot_mode:bios'
|
task.node.properties['capabilities'] = 'boot_mode:bios'
|
||||||
@ -595,6 +600,8 @@ class PXEBootTestCase(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:
|
||||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
||||||
task, ipxe_enabled=False)
|
task, ipxe_enabled=False)
|
||||||
|
dhcp_opts += pxe_utils.dhcp_options_for_instance(
|
||||||
|
task, ipxe_enabled=False, ip_version=6)
|
||||||
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
||||||
task.node.uuid)
|
task.node.uuid)
|
||||||
task.node.properties['capabilities'] = 'boot_mode:bios'
|
task.node.properties['capabilities'] = 'boot_mode:bios'
|
||||||
@ -634,6 +641,8 @@ class PXEBootTestCase(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:
|
||||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
||||||
task, ipxe_enabled=False)
|
task, ipxe_enabled=False)
|
||||||
|
dhcp_opts += pxe_utils.dhcp_options_for_instance(
|
||||||
|
task, ipxe_enabled=False, ip_version=6)
|
||||||
task.node.properties['capabilities'] = 'boot_mode:bios'
|
task.node.properties['capabilities'] = 'boot_mode:bios'
|
||||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||||
|
|
||||||
@ -663,6 +672,8 @@ class PXEBootTestCase(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:
|
||||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
||||||
task, ipxe_enabled=False)
|
task, ipxe_enabled=False)
|
||||||
|
dhcp_opts += pxe_utils.dhcp_options_for_instance(
|
||||||
|
task, ipxe_enabled=False, ip_version=6)
|
||||||
task.node.properties['capabilities'] = 'boot_mode:bios'
|
task.node.properties['capabilities'] = 'boot_mode:bios'
|
||||||
task.node.driver_internal_info['is_whole_disk_image'] = True
|
task.node.driver_internal_info['is_whole_disk_image'] = True
|
||||||
task.driver.boot.prepare_instance(task)
|
task.driver.boot.prepare_instance(task)
|
||||||
@ -734,6 +745,8 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
task.node.save()
|
task.node.save()
|
||||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
||||||
task, ipxe_enabled=False)
|
task, ipxe_enabled=False)
|
||||||
|
dhcp_opts += pxe_utils.dhcp_options_for_instance(
|
||||||
|
task, ipxe_enabled=False, ip_version=6)
|
||||||
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
||||||
task.node.uuid)
|
task.node.uuid)
|
||||||
task.driver.boot.prepare_instance(task)
|
task.driver.boot.prepare_instance(task)
|
||||||
@ -830,6 +843,8 @@ class PXERamdiskDeployTestCase(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:
|
||||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
||||||
task, ipxe_enabled=False)
|
task, ipxe_enabled=False)
|
||||||
|
dhcp_opts += pxe_utils.dhcp_options_for_instance(
|
||||||
|
task, ipxe_enabled=False, ip_version=6)
|
||||||
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
||||||
task.node.uuid)
|
task.node.uuid)
|
||||||
task.node.properties['capabilities'] = 'boot_option:netboot'
|
task.node.properties['capabilities'] = 'boot_option:netboot'
|
||||||
|
16
releasenotes/notes/dual-stack-ironic-493ebc7b71263aaa.yaml
Normal file
16
releasenotes/notes/dual-stack-ironic-493ebc7b71263aaa.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Adds functionality with neutron integration to support dual-stack
|
||||||
|
(IPv4 and IPv6 environment configurations). This enables ironic to
|
||||||
|
look up the attached port(s) and supply DHCP options in alignment
|
||||||
|
with the protocol version allocated on the port.
|
||||||
|
upgrade:
|
||||||
|
- |
|
||||||
|
The ``[pxe]ip_version`` setting may no longer be required depending on
|
||||||
|
neutron integration.
|
||||||
|
- |
|
||||||
|
Operators that used the ``[DEFAULT]my_ip`` setting with an IPv6 address
|
||||||
|
may wish to explore migrating to the ``[DEFAULT]my_ipv6`` setting. Setting
|
||||||
|
both values enables the appropriate IP addresses based on protocol version
|
||||||
|
for PXE/iPXE.
|
Loading…
x
Reference in New Issue
Block a user