From 2d376a76c9298ce225c9dc49c16e6fd92aca55cd Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Wed, 30 Oct 2024 00:58:16 +0000 Subject: [PATCH] [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 a298a37fe7ee41d25db02fdde36e134b01ef5d9a) --- neutron/db/ovn_revision_numbers_db.py | 28 +++++++++++++++---- .../ovn/mech_driver/ovsdb/ovn_client.py | 3 +- .../ovn/mech_driver/ovsdb/test_maintenance.py | 3 +- .../unit/db/test_ovn_revision_numbers_db.py | 20 ++++++++++++- 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/neutron/db/ovn_revision_numbers_db.py b/neutron/db/ovn_revision_numbers_db.py index c7e945cf1d0..3bc91c889cb 100644 --- a/neutron/db/ovn_revision_numbers_db.py +++ b/neutron/db/ovn_revision_numbers_db.py @@ -92,6 +92,12 @@ class UnknownResourceType(n_exc.NeutronException): message = 'Uknown resource type: %(resource_type)s' +# NOTE(ralonsoh): to be moved to neutron-lib +class RevisionNumberNotDefined(n_exc.NeutronException): + message = ('Unique revision number not found for %(resource_uuid)s, ' + 'the resource type is required in query') + + def _get_standard_attr_id(context, resource_uuid, resource_type): try: row = context.session.query(STD_ATTR_MAP[resource_type]).filter_by( @@ -155,14 +161,26 @@ def _ensure_revision_row_exist(context, resource, resource_type, std_attr_id): @db_api.retry_if_session_inactive() -def get_revision_row(context, resource_uuid): +@db_api.CONTEXT_READER +def get_revision_row(context, resource_uuid, resource_type=None): + """Retrieve the resource revision number + + Only the Neutron ports can have two revision number registers, one for the + Logical_Switch_Port and another for the Logical_Router_Port, if this port + is a router interface. It is not strictly needed to filter by resource_type + if the resource is not a port. + """ try: - with db_api.CONTEXT_READER.using(context): - return context.session.query( - ovn_models.OVNRevisionNumbers).filter_by( - resource_uuid=resource_uuid).one() + filters = {'resource_uuid': resource_uuid} + if resource_type: + filters['resource_type'] = resource_type + return context.session.query( + ovn_models.OVNRevisionNumbers).filter_by( + **filters).one() except exc.NoResultFound: pass + except exc.MultipleResultsFound: + raise RevisionNumberNotDefined(resource_uuid=resource_uuid) @db_api.retry_if_session_inactive() diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py index 2b5adcad3c1..890ba143485 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py @@ -838,7 +838,8 @@ class OVNClient(object): # to allow at least one maintenance cycle before we delete the # revision number so that the port doesn't stale and eventually # gets deleted by the maintenance task. - rev_row = db_rev.get_revision_row(context, port_id) + rev_row = db_rev.get_revision_row( + context, port_id, resource_type=ovn_const.TYPE_PORTS) time_ = (timeutils.utcnow() - datetime.timedelta( seconds=ovn_const.DB_CONSISTENCY_CHECK_INTERVAL + 30)) if rev_row and rev_row.created_at >= time_: diff --git a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py index 00ec7932099..5c631ee2c0a 100644 --- a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py +++ b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py @@ -380,7 +380,8 @@ class TestMaintenance(_TestMaintenanceHelper): # Assert the revision number no longer exists self.assertIsNone(db_rev.get_revision_row( self.context, - neutron_obj['id'])) + neutron_obj['id'], + resource_type=ovn_const.TYPE_PORTS)) def test_subnet_global_dhcp4_opts(self): obj_name = 'globaltestsubnet' diff --git a/neutron/tests/unit/db/test_ovn_revision_numbers_db.py b/neutron/tests/unit/db/test_ovn_revision_numbers_db.py index 744c6e62553..9dd714c9221 100644 --- a/neutron/tests/unit/db/test_ovn_revision_numbers_db.py +++ b/neutron/tests/unit/db/test_ovn_revision_numbers_db.py @@ -24,6 +24,7 @@ from oslo_db import exception as db_exc from neutron.api import extensions from neutron.common import config +from neutron.common.ovn import constants as ovn_const from neutron.db.models import ovn as ovn_models from neutron.db import ovn_revision_numbers_db as ovn_rn_db import neutron.extensions @@ -32,7 +33,6 @@ from neutron.tests.unit.db import test_db_base_plugin_v2 from neutron.tests.unit.extensions import test_l3 from neutron.tests.unit.extensions import test_securitygroup - EXTENSIONS_PATH = ':'.join(neutron.extensions.__path__) PLUGIN_CLASS = ( 'neutron.tests.unit.db.test_ovn_revision_numbers_db.TestMaintenancePlugin') @@ -123,6 +123,24 @@ class TestRevisionNumber(test_db_base_plugin_v2.NeutronDbPluginV2TestCase): self.fail("create_initial_revision shouldn't raise " "DBDuplicateEntry when may_exist is True") + def test_get_revision_row_ports(self): + res = self._create_port(self.fmt, self.net['id']) + port = self.deserialize(self.fmt, res)['port'] + with db_api.CONTEXT_WRITER.using(self.ctx): + for resource_type in (ovn_const.TYPE_PORTS, + ovn_const.TYPE_ROUTER_PORTS): + self._create_initial_revision(port['id'], resource_type) + + for resource_type in (ovn_const.TYPE_PORTS, + ovn_const.TYPE_ROUTER_PORTS): + row = ovn_rn_db.get_revision_row( + self.ctx, port['id'], resource_type=resource_type) + self.assertEqual(resource_type, row.resource_type) + self.assertEqual(port['id'], row.resource_uuid) + + self.assertRaises(ovn_rn_db.RevisionNumberNotDefined, + ovn_rn_db.get_revision_row, self.ctx, port['id']) + class TestMaintenancePlugin(test_securitygroup.SecurityGroupTestPlugin, test_l3.TestL3NatBasePlugin):