Skip to content

Commit 989892e

Browse files
committed
[OVN] Fix the revision number retrieval method
The "ovn_revision_numbers" table has a unique constraint that is a combination of the "resource_uuid" and the "resource_type". There is a case where the resource_uuid can be the same for two registers. A router interface will create a single Neutron DB register ("ports") but it will require two OVN DB registers ("Logical_Switch_Port" and "Logical_Router_Ports"). In this case is needed to define the "resource_type" when retrieving the revision number. The exception "RevisionNumberNotDefined" will be thrown if only the "resource_uuid" is provided in the related case. Closes-Bug: #2085946 Change-Id: I12079de78773f7409503392d4791848aea90cb7b (cherry picked from commit a298a37)
1 parent a75051c commit 989892e

File tree

4 files changed

+46
-8
lines changed

4 files changed

+46
-8
lines changed

neutron/db/ovn_revision_numbers_db.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ class UnknownResourceType(n_exc.NeutronException):
8888
message = 'Uknown resource type: %(resource_type)s'
8989

9090

91+
# NOTE(ralonsoh): to be moved to neutron-lib
92+
class RevisionNumberNotDefined(n_exc.NeutronException):
93+
message = ('Unique revision number not found for %(resource_uuid)s, '
94+
'the resource type is required in query')
95+
96+
9197
def _get_standard_attr_id(context, resource_uuid, resource_type):
9298
try:
9399
row = context.session.query(STD_ATTR_MAP[resource_type]).filter_by(
@@ -151,14 +157,26 @@ def _ensure_revision_row_exist(context, resource, resource_type, std_attr_id):
151157

152158

153159
@db_api.retry_if_session_inactive()
154-
def get_revision_row(context, resource_uuid):
160+
@db_api.CONTEXT_READER
161+
def get_revision_row(context, resource_uuid, resource_type=None):
162+
"""Retrieve the resource revision number
163+
164+
Only the Neutron ports can have two revision number registers, one for the
165+
Logical_Switch_Port and another for the Logical_Router_Port, if this port
166+
is a router interface. It is not strictly needed to filter by resource_type
167+
if the resource is not a port.
168+
"""
155169
try:
156-
with db_api.CONTEXT_READER.using(context):
157-
return context.session.query(
158-
ovn_models.OVNRevisionNumbers).filter_by(
159-
resource_uuid=resource_uuid).one()
170+
filters = {'resource_uuid': resource_uuid}
171+
if resource_type:
172+
filters['resource_type'] = resource_type
173+
return context.session.query(
174+
ovn_models.OVNRevisionNumbers).filter_by(
175+
**filters).one()
160176
except exc.NoResultFound:
161177
pass
178+
except exc.MultipleResultsFound:
179+
raise RevisionNumberNotDefined(resource_uuid=resource_uuid)
162180

163181

164182
@db_api.retry_if_session_inactive()

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,8 @@ def delete_port(self, context, port_id, port_object=None):
818818
# to allow at least one maintenance cycle before we delete the
819819
# revision number so that the port doesn't stale and eventually
820820
# gets deleted by the maintenance task.
821-
rev_row = db_rev.get_revision_row(context, port_id)
821+
rev_row = db_rev.get_revision_row(
822+
context, port_id, resource_type=ovn_const.TYPE_PORTS)
822823
time_ = (timeutils.utcnow() - datetime.timedelta(
823824
seconds=ovn_const.DB_CONSISTENCY_CHECK_INTERVAL + 30))
824825
if rev_row and rev_row.created_at >= time_:

neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,8 @@ def test_port(self):
371371
# Assert the revision number no longer exists
372372
self.assertIsNone(db_rev.get_revision_row(
373373
self.context,
374-
neutron_obj['id']))
374+
neutron_obj['id'],
375+
resource_type=ovn_const.TYPE_PORTS))
375376

376377
def test_subnet_global_dhcp4_opts(self):
377378
obj_name = 'globaltestsubnet'

neutron/tests/unit/db/test_ovn_revision_numbers_db.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
from neutron.api import extensions
2626
from neutron.common import config
27+
from neutron.common.ovn import constants as ovn_const
2728
from neutron.db.models import ovn as ovn_models
2829
from neutron.db import ovn_revision_numbers_db as ovn_rn_db
2930
import neutron.extensions
@@ -32,7 +33,6 @@
3233
from neutron.tests.unit.extensions import test_l3
3334
from neutron.tests.unit.extensions import test_securitygroup
3435

35-
3636
EXTENSIONS_PATH = ':'.join(neutron.extensions.__path__)
3737
PLUGIN_CLASS = (
3838
'neutron.tests.unit.db.test_ovn_revision_numbers_db.TestMaintenancePlugin')
@@ -123,6 +123,24 @@ def test_create_initial_revision_may_exist_duplicated_entry(self):
123123
self.fail("create_initial_revision shouldn't raise "
124124
"DBDuplicateEntry when may_exist is True")
125125

126+
def test_get_revision_row_ports(self):
127+
res = self._create_port(self.fmt, self.net['id'])
128+
port = self.deserialize(self.fmt, res)['port']
129+
with db_api.CONTEXT_WRITER.using(self.ctx):
130+
for resource_type in (ovn_const.TYPE_PORTS,
131+
ovn_const.TYPE_ROUTER_PORTS):
132+
self._create_initial_revision(port['id'], resource_type)
133+
134+
for resource_type in (ovn_const.TYPE_PORTS,
135+
ovn_const.TYPE_ROUTER_PORTS):
136+
row = ovn_rn_db.get_revision_row(
137+
self.ctx, port['id'], resource_type=resource_type)
138+
self.assertEqual(resource_type, row.resource_type)
139+
self.assertEqual(port['id'], row.resource_uuid)
140+
141+
self.assertRaises(ovn_rn_db.RevisionNumberNotDefined,
142+
ovn_rn_db.get_revision_row, self.ctx, port['id'])
143+
126144

127145
class TestMaintenancePlugin(test_securitygroup.SecurityGroupTestPlugin,
128146
test_l3.TestL3NatBasePlugin):

0 commit comments

Comments
 (0)