Skip to content

Commit 10a981a

Browse files
committed
[OVN] Retry retrieving LSP hosting information
There's a sync issue while trying to fetch the hosting information for the LSP before we write it to the OVN database, sometimes the information is not yet present and we end up with an empty string ("") for the host attribute of portbindings. This patch adds a retry mechanism to solve this sync issue. Conflicts: neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_client.py Change-Id: I52ec4b346271889ebaa7b7f84981eae5503d02d3 Related-Bug: #2020058 Signed-off-by: Lucas Alvares Gomes <[email protected]> (cherry picked from commit 3044b93)
1 parent 3944dff commit 10a981a

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)