Skip to content

Commit 5d97b13

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "[OVN] Retry retrieving LSP hosting information" into stable/yoga
2 parents b5a3c70 + 10a981a commit 5d97b13

File tree

2 files changed

+123
-1
lines changed

2 files changed

+123
-1
lines changed

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/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):

0 commit comments

Comments
 (0)