[DHCP agent] Add route to OVN metadata port if exists
When DHCP agent is deployed with ml2/ovn for baremetal
ports, ovn metadata route is not added.
This patch adds route via ovn metadata port if exists
so baremetal nodes can fetch metadata.
Closes-Bug: #1982569
Related-Bug: https://bugzilla.redhat.com/show_bug.cgi?id=2213862
Change-Id: I12e496d70bb6db707b317d0aeb6e4edd6c43571e
(cherry picked from commit 82f2a21d1c
)
This commit is contained in:
parent
6cca803b28
commit
a616d6ac91
@ -1132,6 +1132,14 @@ class Dnsmasq(DhcpLocalProcess):
|
||||
file_utils.replace_file(name, '\n'.join(options))
|
||||
return name
|
||||
|
||||
def _get_ovn_metadata_port_ip(self, subnet):
|
||||
m_ports = [port for port in self.network.ports if
|
||||
self._is_ovn_metadata_port(port, self.network.id)]
|
||||
if m_ports:
|
||||
for fixed_ip in m_ports[0].fixed_ips:
|
||||
if fixed_ip.subnet_id == subnet.id:
|
||||
return fixed_ip.ip_address
|
||||
|
||||
def _generate_opts_per_subnet(self):
|
||||
options = []
|
||||
subnets_without_nameservers = set()
|
||||
@ -1185,23 +1193,33 @@ class Dnsmasq(DhcpLocalProcess):
|
||||
else:
|
||||
host_routes.append("%s,%s" % (hr.destination, hr.nexthop))
|
||||
|
||||
# Add host routes for isolated network segments
|
||||
# Determine metadata port route
|
||||
if subnet.ip_version == constants.IP_VERSION_4:
|
||||
metadata_route_ip = None
|
||||
# NOTE: OVN metadata port IP is used in a case when the DHCP
|
||||
# agent is deployed in the ML2/OVN enviroment where the native
|
||||
# ovn-controller dhcp is disabled. The ovn metadata route
|
||||
# takes precedence over native force_metadata and
|
||||
# enable_isolated_metadata routes settings.
|
||||
ovn_metadata_port_ip = self._get_ovn_metadata_port_ip(subnet)
|
||||
if ovn_metadata_port_ip:
|
||||
metadata_route_ip = ovn_metadata_port_ip
|
||||
|
||||
if ((self.conf.force_metadata or
|
||||
(isolated_subnets[subnet.id] and
|
||||
self.conf.enable_isolated_metadata)) and
|
||||
subnet.ip_version == 4):
|
||||
subnet_dhcp_ip = subnet_to_interface_ip.get(subnet.id)
|
||||
if subnet_dhcp_ip:
|
||||
elif (self.conf.force_metadata or
|
||||
(isolated_subnets[subnet.id] and
|
||||
self.conf.enable_isolated_metadata)):
|
||||
subnet_dhcp_ip = subnet_to_interface_ip.get(subnet.id)
|
||||
if subnet_dhcp_ip:
|
||||
metadata_route_ip = subnet_dhcp_ip
|
||||
|
||||
if not isolated_subnets[subnet.id] and gateway:
|
||||
metadata_route_ip = gateway
|
||||
|
||||
if metadata_route_ip:
|
||||
host_routes.append(
|
||||
'%s,%s' % (constants.METADATA_CIDR, subnet_dhcp_ip)
|
||||
'%s,%s' % (constants.METADATA_CIDR, metadata_route_ip)
|
||||
)
|
||||
elif not isolated_subnets[subnet.id] and gateway:
|
||||
host_routes.append(
|
||||
'%s,%s' % (constants.METADATA_CIDR, gateway)
|
||||
)
|
||||
|
||||
if subnet.ip_version == 4:
|
||||
for s in self._get_all_subnets(self.network):
|
||||
sub_segment_id = getattr(s, 'segment_id', None)
|
||||
if (s.ip_version == 4 and
|
||||
@ -1366,13 +1384,21 @@ class Dnsmasq(DhcpLocalProcess):
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _is_ovn_metadata_port(port, network_id):
|
||||
return (port.device_id == 'ovnmeta-' + network_id and
|
||||
port.device_owner == constants.DEVICE_OWNER_DISTRIBUTED)
|
||||
|
||||
@classmethod
|
||||
def should_enable_metadata(cls, conf, network):
|
||||
"""Determine whether the metadata proxy is needed for a network
|
||||
|
||||
This method returns True for truly isolated networks (ie: not attached
|
||||
to a router) when enable_isolated_metadata is True, or for all the
|
||||
networks when the force_metadata flags is True.
|
||||
If the given network contains a ovn metadata port then this method
|
||||
assumes that the ovn metadata service is in use and this metadata
|
||||
service is not required, method returns False. For other cases this
|
||||
method returns True for truly isolated networks (ie: not attached to a
|
||||
router) when enable_isolated_metadata is True, or for all the networks
|
||||
when the force_metadata flags is True.
|
||||
|
||||
This method also returns True when enable_metadata_network is True,
|
||||
and the network passed as a parameter has a subnet in the link-local
|
||||
@ -1381,6 +1407,10 @@ class Dnsmasq(DhcpLocalProcess):
|
||||
providing access to the metadata service via logical routers built
|
||||
with 3rd party backends.
|
||||
"""
|
||||
for port in network.ports:
|
||||
if cls._is_ovn_metadata_port(port, network.id):
|
||||
return False
|
||||
|
||||
all_subnets = cls._get_all_subnets(network)
|
||||
dhcp_subnets = [s for s in all_subnets if s.enable_dhcp]
|
||||
if not dhcp_subnets:
|
||||
|
@ -142,10 +142,27 @@ fake_port2 = dhcp.DictModel(id='12345678-1234-aaaa-123456789000',
|
||||
|
||||
fake_ipv6_port = dhcp.DictModel(id='12345678-1234-aaaa-123456789000',
|
||||
device_owner='',
|
||||
device_id='',
|
||||
mac_address='aa:bb:cc:dd:ee:99',
|
||||
network_id=FAKE_NETWORK_UUID,
|
||||
fixed_ips=[fake_fixed_ipv6])
|
||||
|
||||
fake_ovn_port = dhcp.DictModel(id='12345678-1234-aaaa-123456789000',
|
||||
device_owner='',
|
||||
device_id='',
|
||||
mac_address='aa:bb:cc:dd:ee:98',
|
||||
network_id=FAKE_NETWORK_UUID,
|
||||
fixed_ips=[fake_fixed_ip2])
|
||||
|
||||
fake_ovn_metadata_port = dhcp.DictModel(id='12345678-1234-aaaa-123456789000',
|
||||
device_owner=const.
|
||||
DEVICE_OWNER_DISTRIBUTED,
|
||||
device_id='ovnmeta-{}'.format(
|
||||
FAKE_NETWORK_UUID),
|
||||
mac_address='aa:bb:cc:dd:ee:99',
|
||||
network_id=FAKE_NETWORK_UUID,
|
||||
fixed_ips=[fake_fixed_ip1])
|
||||
|
||||
fake_meta_port = dhcp.DictModel(id='12345678-1234-aaaa-1234567890ab',
|
||||
mac_address='aa:bb:cc:dd:ee:ff',
|
||||
network_id=FAKE_NETWORK_UUID,
|
||||
@ -175,6 +192,12 @@ fake_network_ipv6 = dhcp.NetModel(id=FAKE_NETWORK_UUID,
|
||||
subnets=[fake_ipv6_subnet],
|
||||
ports=[fake_ipv6_port])
|
||||
|
||||
fake_ovn_network = dhcp.NetModel(id=FAKE_NETWORK_UUID,
|
||||
project_id=FAKE_TENANT_ID,
|
||||
admin_state_up=True,
|
||||
subnets=[fake_ipv6_subnet],
|
||||
ports=[fake_ovn_metadata_port, fake_ovn_port])
|
||||
|
||||
fake_network_ipv6_ipv4 = dhcp.NetModel(
|
||||
id=FAKE_NETWORK_UUID,
|
||||
tenant_id=FAKE_TENANT_ID,
|
||||
@ -808,7 +831,7 @@ class TestDhcpAgentEventHandler(base.BaseTestCase):
|
||||
default_cmd_callback=mock.ANY)
|
||||
|
||||
def _enable_dhcp_helper(self, network, enable_isolated_metadata=False,
|
||||
is_isolated_network=False):
|
||||
is_isolated_network=False, is_ovn_network=False):
|
||||
self.dhcp._process_monitor = mock.Mock()
|
||||
if enable_isolated_metadata:
|
||||
cfg.CONF.set_override('enable_isolated_metadata', True)
|
||||
@ -818,7 +841,8 @@ class TestDhcpAgentEventHandler(base.BaseTestCase):
|
||||
mock.call.get_network_info(network.id)])
|
||||
self.call_driver.assert_called_once_with('enable', network)
|
||||
self.cache.assert_has_calls([mock.call.put(network)])
|
||||
if is_isolated_network and enable_isolated_metadata:
|
||||
if (is_isolated_network and enable_isolated_metadata and not
|
||||
is_ovn_network):
|
||||
self.external_process.assert_has_calls([
|
||||
self._process_manager_constructor_call(),
|
||||
mock.call().enable()], any_order=True)
|
||||
@ -863,6 +887,21 @@ class TestDhcpAgentEventHandler(base.BaseTestCase):
|
||||
enable_isolated_metadata=True,
|
||||
is_isolated_network=False)
|
||||
|
||||
def test_enable_dhcp_helper_enable_metadata_ovn_network(self):
|
||||
# Metadata should not be enabled when the dhcp agent is used
|
||||
# in ML2/OVN where the ovn metadata agent is responsible for the
|
||||
# metadata service.
|
||||
self._enable_dhcp_helper(fake_ovn_network, is_ovn_network=True)
|
||||
|
||||
def test_enable_dhcp_helper_ovn_network_with_enable_isolated_metadata(
|
||||
self):
|
||||
# Metadata should not be enabled when the dhcp agent is used
|
||||
# in ML2/OVN where the ovn metadata agent is responsible for the
|
||||
# metadata service. Even if the enable_isolated_metadata is enabled
|
||||
self._enable_dhcp_helper(fake_ovn_network,
|
||||
enable_isolated_metadata=True,
|
||||
is_ovn_network=True)
|
||||
|
||||
def test_enable_dhcp_helper_enable_metadata_empty_network(self):
|
||||
self._enable_dhcp_helper(empty_network,
|
||||
enable_isolated_metadata=True,
|
||||
|
@ -87,6 +87,19 @@ class FakeDhcpPort(object):
|
||||
self.extra_dhcp_opts = []
|
||||
|
||||
|
||||
class FakeOvnMetadataPort(object):
|
||||
def __init__(self):
|
||||
self.id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaa'
|
||||
self.admin_state_up = True
|
||||
self.device_owner = constants.DEVICE_OWNER_DISTRIBUTED
|
||||
self.fixed_ips = [
|
||||
FakeIPAllocation('192.168.0.10',
|
||||
'dddddddd-dddd-dddd-dddd-dddddddddddd')]
|
||||
self.mac_address = '00:00:80:aa:bb:ee'
|
||||
self.device_id = 'ovnmeta-aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
||||
self.extra_dhcp_opts = []
|
||||
|
||||
|
||||
class FakeReservedPort(object):
|
||||
def __init__(self, id='reserved-aaaa-aaaa-aaaa-aaaaaaaaaaa'):
|
||||
self.admin_state_up = True
|
||||
@ -754,6 +767,14 @@ class FakeNetworkDhcpPort(object):
|
||||
self.namespace = 'qdhcp-ns'
|
||||
|
||||
|
||||
class FakeNetworkDhcpandOvnMetadataPort(object):
|
||||
def __init__(self):
|
||||
self.id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
||||
self.subnets = [FakeV4Subnet()]
|
||||
self.ports = [FakePort1(), FakeDhcpPort(), FakeOvnMetadataPort()]
|
||||
self.namespace = 'qdhcp-ns'
|
||||
|
||||
|
||||
class FakeDualNetworkGatewayRoute(object):
|
||||
def __init__(self):
|
||||
self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc'
|
||||
@ -3049,6 +3070,10 @@ class TestDnsmasq(TestBase):
|
||||
self.assertFalse(dhcp.Dnsmasq.has_metadata_subnet(
|
||||
[FakeV4Subnet()]))
|
||||
|
||||
def test_should_enable_metadata_ovn_metadata_port_returns_false(self):
|
||||
self.assertFalse(dhcp.Dnsmasq.should_enable_metadata(
|
||||
self.conf, FakeNetworkDhcpandOvnMetadataPort()))
|
||||
|
||||
def test_should_enable_metadata_isolated_network_returns_true(self):
|
||||
self.assertTrue(dhcp.Dnsmasq.should_enable_metadata(
|
||||
self.conf, FakeV4NetworkNoRouter()))
|
||||
@ -3097,6 +3122,12 @@ class TestDnsmasq(TestBase):
|
||||
'force_metadata': False}
|
||||
self._test__generate_opts_per_subnet_helper(config, False)
|
||||
|
||||
def test__generate_opts_per_subnet_with_metadata_port(self):
|
||||
config = {'enable_isolated_metadata': False,
|
||||
'force_metadata': False}
|
||||
self._test__generate_opts_per_subnet_helper(config, True,
|
||||
network_class=FakeNetworkDhcpandOvnMetadataPort)
|
||||
|
||||
def test__generate_opts_per_subnet_isolated_metadata_with_router(self):
|
||||
config = {'enable_isolated_metadata': True,
|
||||
'force_metadata': False}
|
||||
|
@ -0,0 +1,9 @@
|
||||
---
|
||||
fixes:
|
||||
- |
|
||||
Fixed the scenario where the DHCP agent is deployed in conjunction with
|
||||
the OVN metadata agent in order to serve metadata for baremetal nodes.
|
||||
In this scenario, the DHCP agent would not set the route needed for the
|
||||
OVN metadata agent service resulting in baremetal nodes not being able
|
||||
to query the metadata service. For more information see
|
||||
`bug 1982569 <https://bugs.launchpad.net/neutron/+bug/1982569>`_.
|
Loading…
Reference in New Issue
Block a user