Skip to content

Commit f6c1b15

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "[DHCP agent] Add route to OVN metadata port if exists" into stable/2023.1
2 parents 2b611b7 + 07c3eb4 commit f6c1b15

File tree

4 files changed

+128
-19
lines changed

4 files changed

+128
-19
lines changed

neutron/agent/linux/dhcp.py

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,6 +1209,14 @@ def _output_opts_file(self):
12091209
file_utils.replace_file(name, '\n'.join(options))
12101210
return name
12111211

1212+
def _get_ovn_metadata_port_ip(self, subnet):
1213+
m_ports = [port for port in self.network.ports if
1214+
self._is_ovn_metadata_port(port, self.network.id)]
1215+
if m_ports:
1216+
for fixed_ip in m_ports[0].fixed_ips:
1217+
if fixed_ip.subnet_id == subnet.id:
1218+
return fixed_ip.ip_address
1219+
12121220
def _generate_opts_per_subnet(self):
12131221
options = []
12141222
subnets_without_nameservers = set()
@@ -1262,23 +1270,33 @@ def _generate_opts_per_subnet(self):
12621270
else:
12631271
host_routes.append("%s,%s" % (hr.destination, hr.nexthop))
12641272

1265-
# Add host routes for isolated network segments
1266-
1267-
if ((self.conf.force_metadata or
1268-
(isolated_subnets[subnet.id] and
1269-
self.conf.enable_isolated_metadata)) and
1270-
subnet.ip_version == 4):
1271-
subnet_dhcp_ip = subnet_to_interface_ip.get(subnet.id)
1272-
if subnet_dhcp_ip:
1273+
# Determine metadata port route
1274+
if subnet.ip_version == constants.IP_VERSION_4:
1275+
metadata_route_ip = None
1276+
# NOTE: OVN metadata port IP is used in a case when the DHCP
1277+
# agent is deployed in the ML2/OVN enviroment where the native
1278+
# ovn-controller dhcp is disabled. The ovn metadata route
1279+
# takes precedence over native force_metadata and
1280+
# enable_isolated_metadata routes settings.
1281+
ovn_metadata_port_ip = self._get_ovn_metadata_port_ip(subnet)
1282+
if ovn_metadata_port_ip:
1283+
metadata_route_ip = ovn_metadata_port_ip
1284+
1285+
elif (self.conf.force_metadata or
1286+
(isolated_subnets[subnet.id] and
1287+
self.conf.enable_isolated_metadata)):
1288+
subnet_dhcp_ip = subnet_to_interface_ip.get(subnet.id)
1289+
if subnet_dhcp_ip:
1290+
metadata_route_ip = subnet_dhcp_ip
1291+
1292+
if not isolated_subnets[subnet.id] and gateway:
1293+
metadata_route_ip = gateway
1294+
1295+
if metadata_route_ip:
12731296
host_routes.append(
1274-
'%s,%s' % (constants.METADATA_CIDR, subnet_dhcp_ip)
1297+
'%s,%s' % (constants.METADATA_CIDR, metadata_route_ip)
12751298
)
1276-
elif not isolated_subnets[subnet.id] and gateway:
1277-
host_routes.append(
1278-
'%s,%s' % (constants.METADATA_CIDR, gateway)
1279-
)
12801299

1281-
if subnet.ip_version == 4:
12821300
for s in self._get_all_subnets(self.network):
12831301
sub_segment_id = getattr(s, 'segment_id', None)
12841302
if (s.ip_version == 4 and
@@ -1443,13 +1461,21 @@ def has_metadata_subnet(subnets):
14431461
return True
14441462
return False
14451463

1464+
@staticmethod
1465+
def _is_ovn_metadata_port(port, network_id):
1466+
return (port.device_id == 'ovnmeta-' + network_id and
1467+
port.device_owner == constants.DEVICE_OWNER_DISTRIBUTED)
1468+
14461469
@classmethod
14471470
def should_enable_metadata(cls, conf, network):
14481471
"""Determine whether the metadata proxy is needed for a network
14491472
1450-
This method returns True for truly isolated networks (ie: not attached
1451-
to a router) when enable_isolated_metadata is True, or for all the
1452-
networks when the force_metadata flags is True.
1473+
If the given network contains a ovn metadata port then this method
1474+
assumes that the ovn metadata service is in use and this metadata
1475+
service is not required, method returns False. For other cases this
1476+
method returns True for truly isolated networks (ie: not attached to a
1477+
router) when enable_isolated_metadata is True, or for all the networks
1478+
when the force_metadata flags is True.
14531479
14541480
This method also returns True when enable_metadata_network is True,
14551481
and the network passed as a parameter has a subnet in the link-local
@@ -1458,6 +1484,10 @@ def should_enable_metadata(cls, conf, network):
14581484
providing access to the metadata service via logical routers built
14591485
with 3rd party backends.
14601486
"""
1487+
for port in network.ports:
1488+
if cls._is_ovn_metadata_port(port, network.id):
1489+
return False
1490+
14611491
all_subnets = cls._get_all_subnets(network)
14621492
dhcp_subnets = [s for s in all_subnets if s.enable_dhcp]
14631493
if not dhcp_subnets:

neutron/tests/unit/agent/dhcp/test_agent.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,27 @@
152152

153153
fake_ipv6_port = dhcp.DictModel(id='12345678-1234-aaaa-123456789000',
154154
device_owner='',
155+
device_id='',
155156
mac_address='aa:bb:cc:dd:ee:99',
156157
network_id=FAKE_NETWORK_UUID,
157158
fixed_ips=[fake_fixed_ipv6])
158159

160+
fake_ovn_port = dhcp.DictModel(id='12345678-1234-aaaa-123456789000',
161+
device_owner='',
162+
device_id='',
163+
mac_address='aa:bb:cc:dd:ee:98',
164+
network_id=FAKE_NETWORK_UUID,
165+
fixed_ips=[fake_fixed_ip2])
166+
167+
fake_ovn_metadata_port = dhcp.DictModel(id='12345678-1234-aaaa-123456789000',
168+
device_owner=const.
169+
DEVICE_OWNER_DISTRIBUTED,
170+
device_id='ovnmeta-{}'.format(
171+
FAKE_NETWORK_UUID),
172+
mac_address='aa:bb:cc:dd:ee:99',
173+
network_id=FAKE_NETWORK_UUID,
174+
fixed_ips=[fake_fixed_ip1])
175+
159176
fake_meta_port = dhcp.DictModel(id='12345678-1234-aaaa-1234567890ab',
160177
mac_address='aa:bb:cc:dd:ee:ff',
161178
network_id=FAKE_NETWORK_UUID,
@@ -191,6 +208,12 @@
191208
subnets=[fake_ipv6_subnet],
192209
ports=[fake_ipv6_port])
193210

211+
fake_ovn_network = dhcp.NetModel(id=FAKE_NETWORK_UUID,
212+
project_id=FAKE_PROJECT_ID,
213+
admin_state_up=True,
214+
subnets=[fake_ipv6_subnet],
215+
ports=[fake_ovn_metadata_port, fake_ovn_port])
216+
194217
fake_network_ipv6_ipv4 = dhcp.NetModel(
195218
id=FAKE_NETWORK_UUID,
196219
project_id=FAKE_PROJECT_ID,
@@ -803,7 +826,7 @@ def _process_manager_constructor_call(self, ns=FAKE_NETWORK_DHCP_NS):
803826
default_cmd_callback=mock.ANY)
804827

805828
def _enable_dhcp_helper(self, network, enable_isolated_metadata=False,
806-
is_isolated_network=False):
829+
is_isolated_network=False, is_ovn_network=False):
807830
self.dhcp._process_monitor = mock.Mock()
808831
if enable_isolated_metadata:
809832
cfg.CONF.set_override('enable_isolated_metadata', True)
@@ -813,7 +836,8 @@ def _enable_dhcp_helper(self, network, enable_isolated_metadata=False,
813836
mock.call.get_network_info(network.id)])
814837
self.call_driver.assert_called_once_with('enable', network)
815838
self.cache.assert_has_calls([mock.call.put(network)])
816-
if is_isolated_network and enable_isolated_metadata:
839+
if (is_isolated_network and enable_isolated_metadata and not
840+
is_ovn_network):
817841
self.external_process.assert_has_calls([
818842
self._process_manager_constructor_call(),
819843
mock.call().enable()], any_order=True)
@@ -858,6 +882,21 @@ def test_enable_dhcp_helper_enable_metadata_nonisolated_dist_network(self):
858882
enable_isolated_metadata=True,
859883
is_isolated_network=False)
860884

885+
def test_enable_dhcp_helper_enable_metadata_ovn_network(self):
886+
# Metadata should not be enabled when the dhcp agent is used
887+
# in ML2/OVN where the ovn metadata agent is responsible for the
888+
# metadata service.
889+
self._enable_dhcp_helper(fake_ovn_network, is_ovn_network=True)
890+
891+
def test_enable_dhcp_helper_ovn_network_with_enable_isolated_metadata(
892+
self):
893+
# Metadata should not be enabled when the dhcp agent is used
894+
# in ML2/OVN where the ovn metadata agent is responsible for the
895+
# metadata service. Even if the enable_isolated_metadata is enabled
896+
self._enable_dhcp_helper(fake_ovn_network,
897+
enable_isolated_metadata=True,
898+
is_ovn_network=True)
899+
861900
def test_enable_dhcp_helper_enable_metadata_empty_network(self):
862901
self._enable_dhcp_helper(empty_network,
863902
enable_isolated_metadata=True,

neutron/tests/unit/agent/linux/test_dhcp.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,19 @@ def __init__(self):
9090
self.extra_dhcp_opts = []
9191

9292

93+
class FakeOvnMetadataPort(object):
94+
def __init__(self):
95+
self.id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaa'
96+
self.admin_state_up = True
97+
self.device_owner = constants.DEVICE_OWNER_DISTRIBUTED
98+
self.fixed_ips = [
99+
FakeIPAllocation('192.168.0.10',
100+
'dddddddd-dddd-dddd-dddd-dddddddddddd')]
101+
self.mac_address = '00:00:80:aa:bb:ee'
102+
self.device_id = 'ovnmeta-aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
103+
self.extra_dhcp_opts = []
104+
105+
93106
class FakeReservedPort(object):
94107
def __init__(self, id='reserved-aaaa-aaaa-aaaa-aaaaaaaaaaa'):
95108
self.admin_state_up = True
@@ -768,6 +781,14 @@ def __init__(self):
768781
self.namespace = 'qdhcp-ns'
769782

770783

784+
class FakeNetworkDhcpandOvnMetadataPort(object):
785+
def __init__(self):
786+
self.id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
787+
self.subnets = [FakeV4Subnet()]
788+
self.ports = [FakePort1(), FakeDhcpPort(), FakeOvnMetadataPort()]
789+
self.namespace = 'qdhcp-ns'
790+
791+
771792
class FakeDualNetworkGatewayRoute(object):
772793
def __init__(self):
773794
self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc'
@@ -3129,6 +3150,10 @@ def test_has_metadata_subnet_returns_false(self):
31293150
self.assertFalse(dhcp.Dnsmasq.has_metadata_subnet(
31303151
[FakeV4Subnet()]))
31313152

3153+
def test_should_enable_metadata_ovn_metadata_port_returns_false(self):
3154+
self.assertFalse(dhcp.Dnsmasq.should_enable_metadata(
3155+
self.conf, FakeNetworkDhcpandOvnMetadataPort()))
3156+
31323157
def test_should_enable_metadata_isolated_network_returns_true(self):
31333158
self.assertTrue(dhcp.Dnsmasq.should_enable_metadata(
31343159
self.conf, FakeV4NetworkNoRouter()))
@@ -3177,6 +3202,12 @@ def test__generate_opts_per_subnet_no_metadata(self):
31773202
'force_metadata': False}
31783203
self._test__generate_opts_per_subnet_helper(config, False)
31793204

3205+
def test__generate_opts_per_subnet_with_metadata_port(self):
3206+
config = {'enable_isolated_metadata': False,
3207+
'force_metadata': False}
3208+
self._test__generate_opts_per_subnet_helper(config, True,
3209+
network_class=FakeNetworkDhcpandOvnMetadataPort)
3210+
31803211
def test__generate_opts_per_subnet_isolated_metadata_with_router(self):
31813212
config = {'enable_isolated_metadata': True,
31823213
'force_metadata': False}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
fixes:
3+
- |
4+
Fixed the scenario where the DHCP agent is deployed in conjunction with
5+
the OVN metadata agent in order to serve metadata for baremetal nodes.
6+
In this scenario, the DHCP agent would not set the route needed for the
7+
OVN metadata agent service resulting in baremetal nodes not being able
8+
to query the metadata service. For more information see
9+
`bug 1982569 <https://bugs.launchpad.net/neutron/+bug/1982569>`_.

0 commit comments

Comments
 (0)