Skip to content

Commit 99556e9

Browse files
authored
Merge pull request #63 from stackhpc/upstream/yoga-2023-08-28
Synchronise yoga with upstream
2 parents 516f789 + 7034f4c commit 99556e9

File tree

7 files changed

+212
-10
lines changed

7 files changed

+212
-10
lines changed

neutron/agent/ovn/metadata/agent.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,8 @@ def sync(self):
339339
for ns in ip_lib.list_network_namespaces())
340340
net_datapaths = self.get_networks_datapaths()
341341
metadata_namespaces = [
342-
self._get_namespace_name(str(datapath.uuid))
342+
self._get_namespace_name(
343+
ovn_utils.get_network_name_from_datapath(datapath))
343344
for datapath in net_datapaths
344345
]
345346
unused_namespaces = [ns for ns in system_namespaces if
@@ -348,8 +349,9 @@ def sync(self):
348349
for ns in unused_namespaces:
349350
self.teardown_datapath(self._get_datapath_name(ns))
350351

351-
# now that all obsolete namespaces are cleaned up, deploy required
352-
# networks
352+
# resync all network namespaces based on the associated datapaths,
353+
# even those that are already running. This is to make sure
354+
# everything within each namespace is up to date.
353355
for datapath in net_datapaths:
354356
self.provision_datapath(datapath)
355357

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,15 @@
4242
from ovsdbapp.backend.ovs_idl import idlutils
4343
import tenacity
4444

45+
from neutron._i18n import _
4546
from neutron.common.ovn import acl as ovn_acl
4647
from neutron.common.ovn import constants as ovn_const
4748
from neutron.common.ovn import utils
4849
from neutron.common import utils as common_utils
4950
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
5051
from neutron.db import ovn_revision_numbers_db as db_rev
5152
from neutron.db import segments_db
53+
from neutron.plugins.ml2 import db as ml2_db
5254
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \
5355
import placement as placement_extension
5456
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \
@@ -272,6 +274,29 @@ def determine_bind_host(self, port, port_context=None):
272274
ovn_const.VIF_DETAILS_CARD_SERIAL_NUMBER]).hostname
273275
return ''
274276

277+
@tenacity.retry(retry=tenacity.retry_if_exception_type(RuntimeError),
278+
wait=tenacity.wait_random(min=2, max=3),
279+
stop=tenacity.stop_after_attempt(3),
280+
reraise=True)
281+
def _wait_for_port_bindings_host(self, context, port_id):
282+
db_port = ml2_db.get_port(context, port_id)
283+
# This is already checked previously but, just to stay on
284+
# the safe side in case the port is deleted mid-operation
285+
if not db_port:
286+
raise RuntimeError(
287+
_('No port found with ID %s') % port_id)
288+
289+
if not db_port.port_bindings:
290+
raise RuntimeError(
291+
_('No port bindings information found for '
292+
'port %s') % port_id)
293+
294+
if not db_port.port_bindings[0].host:
295+
raise RuntimeError(
296+
_('No hosting information found for port %s') % port_id)
297+
298+
return db_port
299+
275300
def update_lsp_host_info(self, context, db_port, up=True):
276301
"""Update the binding hosting information for the LSP.
277302
@@ -287,8 +312,19 @@ def update_lsp_host_info(self, context, db_port, up=True):
287312
if up:
288313
if not db_port.port_bindings:
289314
return
290-
host = db_port.port_bindings[0].host
291315

316+
if not db_port.port_bindings[0].host:
317+
# NOTE(lucasgomes): There might be a sync issue between
318+
# the moment that this port was fetched from the database
319+
# and the hosting information being set, retry a few times
320+
try:
321+
db_port = self._wait_for_port_bindings_host(
322+
context, db_port.id)
323+
except RuntimeError as e:
324+
LOG.warning(e)
325+
return
326+
327+
host = db_port.port_bindings[0].host
292328
ext_ids = ('external_ids',
293329
{ovn_const.OVN_HOST_ID_EXT_ID_KEY: host})
294330
cmd.append(

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,36 @@ def _delete_floatingip_pfs(self, context, fip_id, txn):
446446
self.l3_plugin.port_forwarding.db_sync_delete(
447447
context, fip_id, txn)
448448

449+
def _is_router_port_changed(self, db_router_port, lrport_nets):
450+
"""Check if the router port needs to be updated.
451+
452+
This method checks for networks and ipv6_ra_configs (if supported)
453+
changes on a given router port.
454+
"""
455+
db_lrport_nets = db_router_port['networks']
456+
if db_lrport_nets != lrport_nets:
457+
return True
458+
459+
# Check for ipv6_ra_configs changes
460+
db_lrport_ra = db_router_port['ipv6_ra_configs']
461+
lrport_ra = {}
462+
ipv6_ra_supported = self.ovn_api.is_col_present(
463+
'Logical_Router_Port', 'ipv6_ra_configs')
464+
if ipv6_ra_supported:
465+
lrp_name = utils.ovn_lrouter_port_name(db_router_port['id'])
466+
try:
467+
ovn_lrport = self.ovn_api.lrp_get(
468+
lrp_name).execute(check_error=True)
469+
except idlutils.RowNotFound:
470+
# If the port is not found in the OVN database the
471+
# ovn-db-sync script will recreate this port later
472+
# and it will have the latest information. No need
473+
# to update it.
474+
return False
475+
lrport_ra = ovn_lrport.ipv6_ra_configs
476+
477+
return db_lrport_ra != lrport_ra
478+
449479
def sync_routers_and_rports(self, ctx):
450480
"""Sync Routers between neutron and NB.
451481
@@ -525,6 +555,12 @@ def sync_routers_and_rports(self, ctx):
525555
constants.DEVICE_OWNER_HA_REPLICATED_INT])
526556
for interface in interfaces:
527557
db_router_ports[interface['id']] = interface
558+
networks, ipv6_ra_configs = (
559+
self._ovn_client._get_nets_and_ipv6_ra_confs_for_router_port(
560+
ctx, interface))
561+
db_router_ports[interface['id']]['networks'] = networks
562+
db_router_ports[interface['id']][
563+
'ipv6_ra_configs'] = ipv6_ra_configs
528564

529565
lrouters = self.ovn_api.get_all_logical_routers_with_rports()
530566

@@ -541,11 +577,9 @@ def sync_routers_and_rports(self, ctx):
541577
if lrouter['name'] in db_routers:
542578
for lrport, lrport_nets in lrouter['ports'].items():
543579
if lrport in db_router_ports:
544-
# We dont have to check for the networks and
545-
# ipv6_ra_configs values. Lets add it to the
546-
# update_lrport_list. If they are in sync, then
547-
# update_router_port will be a no-op.
548-
update_lrport_list.append(db_router_ports[lrport])
580+
if self._is_router_port_changed(
581+
db_router_ports[lrport], lrport_nets):
582+
update_lrport_list.append(db_router_ports[lrport])
549583
del db_router_ports[lrport]
550584
else:
551585
del_lrouter_ports_list.append(

neutron/tests/unit/agent/ovn/metadata/test_agent.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ def setUp(self):
8383

8484
self.ports = []
8585
for i in range(0, 3):
86-
self.ports.append(makePort(datapath=DatapathInfo(uuid=str(i),
86+
self.ports.append(makePort(
87+
datapath=DatapathInfo(uuid=str(uuid.uuid4()),
8788
external_ids={'name': 'neutron-%d' % i})))
8889
self.agent.sb_idl.get_ports_on_chassis.return_value = self.ports
8990

neutron/tests/unit/fake_resources.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ def __init__(self, **kwargs):
159159
self.ha_chassis_group_del = mock.Mock()
160160
self.ha_chassis_group_add_chassis = mock.Mock()
161161
self.ha_chassis_group_del_chassis = mock.Mock()
162+
self.lrp_get = mock.Mock()
162163

163164

164165
class FakeOvsdbSbOvnIdl(object):

neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_client.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from neutron.common.ovn import constants
1919
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
20+
from neutron.plugins.ml2 import db as ml2_db
2021
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client
2122
from neutron.tests import base
2223
from neutron.tests.unit import fake_resources as fakes
@@ -25,6 +26,8 @@
2526
from neutron_lib.api.definitions import portbindings
2627
from neutron_lib.services.logapi import constants as log_const
2728

29+
from tenacity import wait_none
30+
2831

2932
class TestOVNClientBase(base.BaseTestCase):
3033

@@ -47,6 +50,9 @@ def setUp(self):
4750
fakes.FakeChassis.create(
4851
attrs={'hostname': self.fake_smartnic_hostname}))
4952

53+
# Disable tenacity wait for UT
54+
self.ovn_client._wait_for_port_bindings_host.retry.wait = wait_none()
55+
5056
def test_vnic_normal_unbound_port(self):
5157
self.assertEqual(
5258
'',
@@ -139,6 +145,45 @@ def test_update_lsp_host_info_up(self):
139145
'Logical_Switch_Port', port_id,
140146
('external_ids', {constants.OVN_HOST_ID_EXT_ID_KEY: host_id}))
141147

148+
def test_update_lsp_host_info_up_retry(self):
149+
context = mock.MagicMock()
150+
host_id = 'fake-binding-host-id'
151+
port_id = 'fake-port-id'
152+
db_port_no_host = mock.Mock(
153+
id=port_id, port_bindings=[mock.Mock(host="")])
154+
db_port = mock.Mock(
155+
id=port_id, port_bindings=[mock.Mock(host=host_id)])
156+
157+
with mock.patch.object(
158+
self.ovn_client, '_wait_for_port_bindings_host') as mock_wait:
159+
mock_wait.return_value = db_port
160+
self.ovn_client.update_lsp_host_info(context, db_port_no_host)
161+
162+
# Assert _wait_for_port_bindings_host was called
163+
mock_wait.assert_called_once_with(context, port_id)
164+
165+
# Assert host_id was set
166+
self.nb_idl.db_set.assert_called_once_with(
167+
'Logical_Switch_Port', port_id,
168+
('external_ids', {constants.OVN_HOST_ID_EXT_ID_KEY: host_id}))
169+
170+
def test_update_lsp_host_info_up_retry_fail(self):
171+
context = mock.MagicMock()
172+
port_id = 'fake-port-id'
173+
db_port_no_host = mock.Mock(
174+
id=port_id, port_bindings=[mock.Mock(host="")])
175+
176+
with mock.patch.object(
177+
self.ovn_client, '_wait_for_port_bindings_host') as mock_wait:
178+
mock_wait.side_effect = RuntimeError("boom")
179+
self.ovn_client.update_lsp_host_info(context, db_port_no_host)
180+
181+
# Assert _wait_for_port_bindings_host was called
182+
mock_wait.assert_called_once_with(context, port_id)
183+
184+
# Assert host_id was NOT set
185+
self.assertFalse(self.nb_idl.db_set.called)
186+
142187
def test_update_lsp_host_info_down(self):
143188
context = mock.MagicMock()
144189
port_id = 'fake-port-id'
@@ -150,6 +195,47 @@ def test_update_lsp_host_info_down(self):
150195
'Logical_Switch_Port', port_id, 'external_ids',
151196
constants.OVN_HOST_ID_EXT_ID_KEY, if_exists=True)
152197

198+
@mock.patch.object(ml2_db, 'get_port')
199+
def test__wait_for_port_bindings_host(self, mock_get_port):
200+
context = mock.MagicMock()
201+
host_id = 'fake-binding-host-id'
202+
port_id = 'fake-port-id'
203+
db_port_no_host = mock.Mock(
204+
id=port_id, port_bindings=[mock.Mock(host="")])
205+
db_port = mock.Mock(
206+
id=port_id, port_bindings=[mock.Mock(host=host_id)])
207+
208+
mock_get_port.side_effect = (db_port_no_host, db_port)
209+
210+
ret = self.ovn_client._wait_for_port_bindings_host(
211+
context, port_id)
212+
213+
self.assertEqual(ret, db_port)
214+
215+
expected_calls = [mock.call(context, port_id),
216+
mock.call(context, port_id)]
217+
mock_get_port.assert_has_calls(expected_calls)
218+
219+
@mock.patch.object(ml2_db, 'get_port')
220+
def test__wait_for_port_bindings_host_fail(self, mock_get_port):
221+
context = mock.MagicMock()
222+
port_id = 'fake-port-id'
223+
db_port_no_pb = mock.Mock(id=port_id, port_bindings=[])
224+
db_port_no_host = mock.Mock(
225+
id=port_id, port_bindings=[mock.Mock(host="")])
226+
227+
mock_get_port.side_effect = (
228+
db_port_no_pb, db_port_no_host, db_port_no_host)
229+
230+
self.assertRaises(
231+
RuntimeError, self.ovn_client._wait_for_port_bindings_host,
232+
context, port_id)
233+
234+
expected_calls = [mock.call(context, port_id),
235+
mock.call(context, port_id),
236+
mock.call(context, port_id)]
237+
mock_get_port.assert_has_calls(expected_calls)
238+
153239

154240
class TestOVNClientFairMeter(TestOVNClientBase,
155241
test_log_driver.TestOVNDriverBase):

neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client
2424
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_db_sync
2525
from neutron.services.ovn_l3 import plugin as ovn_plugin
26+
from neutron.tests.unit import fake_resources as fakes
2627
from neutron.tests.unit.plugins.ml2.drivers.ovn.mech_driver import \
2728
test_mech_driver
2829

@@ -1109,6 +1110,47 @@ def test_ovn_nb_sync_calculate_routes_add_remove_keep_two_routes(self):
11091110
expected_deleted)
11101111

11111112

1113+
class TestIsRouterPortChanged(test_mech_driver.OVNMechanismDriverTestCase):
1114+
1115+
def setUp(self):
1116+
super(TestIsRouterPortChanged, self).setUp()
1117+
self.ovn_nb_synchronizer = ovn_db_sync.OvnNbSynchronizer(
1118+
self.plugin, self.mech_driver.nb_ovn, self.mech_driver.sb_ovn,
1119+
'log', self.mech_driver)
1120+
1121+
self.db_router_port = {
1122+
'id': 'aa076509-915d-4b1c-8d9d-3db53d9c5faf',
1123+
'networks': ['fdf9:ad62:3a04::1/64'],
1124+
'ipv6_ra_configs': {'address_mode': 'slaac',
1125+
'send_periodic': 'true',
1126+
'mtu': '1442'}
1127+
}
1128+
self.lrport_nets = ['fdf9:ad62:3a04::1/64']
1129+
self.ovn_lrport = fakes.FakeOvsdbRow.create_one_ovsdb_row(
1130+
attrs={'ipv6_ra_configs': {'address_mode': 'slaac',
1131+
'send_periodic': 'true',
1132+
'mtu': '1442'}})
1133+
1134+
self.ovn_nb_synchronizer.ovn_api.is_col_present.return_value = True
1135+
self.ovn_nb_synchronizer.ovn_api.lrp_get().execute.return_value = (
1136+
self.ovn_lrport)
1137+
1138+
def test__is_router_port_changed_not_changed(self):
1139+
self.assertFalse(self.ovn_nb_synchronizer._is_router_port_changed(
1140+
self.db_router_port, self.lrport_nets))
1141+
1142+
def test__is_router_port_changed_network_changed(self):
1143+
self.db_router_port['networks'] = ['172.24.4.26/24',
1144+
'2001:db8::206/64']
1145+
self.assertTrue(self.ovn_nb_synchronizer._is_router_port_changed(
1146+
self.db_router_port, self.lrport_nets))
1147+
1148+
def test__is_router_port_changed_ipv6_ra_configs_changed(self):
1149+
self.db_router_port['ipv6_ra_configs']['mtu'] = '1500'
1150+
self.assertTrue(self.ovn_nb_synchronizer._is_router_port_changed(
1151+
self.db_router_port, self.lrport_nets))
1152+
1153+
11121154
class TestOvnSbSyncML2(test_mech_driver.OVNMechanismDriverTestCase):
11131155

11141156
def test_ovn_sb_sync(self):

0 commit comments

Comments
 (0)