Add both IPv4 and IPv6 DHCP options if interface has both

It is possible that an interface has both IPv4 and IPv6 addresses,
primarily when using SLAAC with OpenStack Neutron.  When this is
the case, it is very likely that the first fixed IP would be a
SLAAC assigned port and the second IP is the IPv4 address.

In an environment where you are looking to boot via IPv4, no DHCPv6
infrastructure exists as IPv6 connectivity is provided via SLAAC,
you would not be able to use this network to boot off of.

This patch instead grabs all the fixed IP addresses, then inserts
the options that match the IP versions which are attached to the
interface, potentially resulting in both IPv4 and IPv6 options
being included (though the IPv6 ones would be largely omitted).

In environments where only IPv4 or IPv6 is in use on the port, it
will still only insert the options for those specific IP versions.

Story #2008660
Task #41933
Change-Id: I52e4ee022b17cb7f007534cb368136567b139a34
This commit is contained in:
Mohammed Naser 2021-02-25 16:53:55 -05:00 committed by Jay Faulkner
parent f5d9cabef2
commit 367cdcd665
3 changed files with 85 additions and 20 deletions

View File

@ -64,28 +64,28 @@ class NeutronDHCPApi(base.BaseDHCP):
try: try:
neutron_client = neutron.get_client(token=token, context=context) neutron_client = neutron.get_client(token=token, context=context)
fip = None fips = []
port = neutron_client.get_port(port_id) port = neutron_client.get_port(port_id)
try: if port:
if port: # TODO(TheJulia): We need to retool this down the
# TODO(TheJulia): We need to retool this down the # road so that we handle ports and allow preferences
# road so that we handle ports and allow preferences # for multi-address ports with different IP versions
# for multi-address ports with different IP versions # and enable operators to possibly select preferences
# and enable operators to possibly select preferences # for provisionioning operations.
# for provisionioning operations. # This is compounded by v6 mainly only being available
# This is compounded by v6 mainly only being available # with UEFI machines, so the support matrix also gets
# with UEFI machines, so the support matrix also gets # a little "weird".
# a little "weird". # Ideally, we should work on this in Victoria.
# Ideally, we should work on this in Victoria. fips = port.get('fixed_ips')
fip = port.get('fixed_ips')[0]
except (TypeError, IndexError):
fip = None
update_opts = [] update_opts = []
if fip: if len(fips) != 0:
ip_version = ipaddress.ip_address(fip['ip_address']).version for fip in fips:
for option in dhcp_options: ip_version = \
if option.get('ip_version', 4) == ip_version: ipaddress.ip_address(fip['ip_address']).version
update_opts.append(option) for option in dhcp_options:
if option.get('ip_version', 4) == ip_version:
update_opts.append(option)
else: else:
LOG.error('Requested to update port for port %s, ' LOG.error('Requested to update port for port %s, '
'however port lacks an IP address.', port_id) 'however port lacks an IP address.', port_id)

View File

@ -119,6 +119,66 @@ 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)
def test_update_port_dhcp_opts_v4_and_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 = {
'extra_dhcp_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_data = {
"id": port_id,
"fixed_ips": [
{
"ip_address": "192.168.1.3",
},
{
"ip_address": "2001:db8::201",
}
],
}
client_mock.return_value.get_port.return_value = 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(
task.context, port_id, expected)
@mock.patch('ironic.common.neutron.get_client', autospec=True) @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,

View File

@ -0,0 +1,5 @@
---
fixes:
- When using the Neutron DHCP driver, Ironic would only use the first fixed
IP address to determine what IP versions are use on the port. Now, it
checks for all the IP addresses and adds DHCP options for all IP versions.