diff --git a/gbpservice/neutron/plugins/ml2plus/managers.py b/gbpservice/neutron/plugins/ml2plus/managers.py index dd61db377..7819b0914 100644 --- a/gbpservice/neutron/plugins/ml2plus/managers.py +++ b/gbpservice/neutron/plugins/ml2plus/managers.py @@ -20,6 +20,7 @@ from gbpservice.neutron.plugins.ml2plus import driver_api from neutron.db import api as db_api from neutron.plugins.ml2.common import exceptions as ml2_exc from neutron.plugins.ml2 import managers +from neutron.quota import resource_registry from oslo_log import log from oslo_utils import excutils @@ -31,6 +32,25 @@ class MechanismManager(managers.MechanismManager): def __init__(self): super(MechanismManager, self).__init__() + def _call_on_drivers(self, method_name, context, + continue_on_failure=False, raise_db_retriable=False): + super(MechanismManager, self)._call_on_drivers( + method_name, context, continue_on_failure=False, + raise_db_retriable=False) + if method_name.endswith('_precommit'): + # This does the same thing as: + # https://github.com/openstack/neutron/blob/newton-eol/neutron/ + # api/v2/base.py#L489 + # but from within the scope of the plugin's transaction, such + # that if it fails, everything that happened prior to this in + # precommit phase can also be rolled back. + resource_name = method_name.replace('_precommit', '').replace( + 'create_', '').replace('update_', '').replace( + 'delete_', '') + tracked_resource = resource_registry.get_resource(resource_name) + tracked_resource._dirty_tenants.add(context.current['tenant_id']) + resource_registry.set_resources_dirty(context._plugin_context) + def _call_on_extended_drivers(self, method_name, context, continue_on_failure=False, raise_db_retriable=False): diff --git a/gbpservice/neutron/plugins/ml2plus/patch_neutron.py b/gbpservice/neutron/plugins/ml2plus/patch_neutron.py index 51a584935..7fd9b6d58 100644 --- a/gbpservice/neutron/plugins/ml2plus/patch_neutron.py +++ b/gbpservice/neutron/plugins/ml2plus/patch_neutron.py @@ -166,10 +166,13 @@ def undecorated(o): else: return o - +from neutron.db import common_db_mixin as common_db_api from neutron.db.quota import api as quota_api from neutron.db.quota import driver # noqa +from neutron.db.quota import models as quota_models from neutron import quota +from neutron.quota import resource_registry as res_reg +from oslo_config import cfg f = quota_api.remove_reservation @@ -183,6 +186,37 @@ def commit_reservation(context, reservation_id): quota.QUOTAS.get_driver().commit_reservation = commit_reservation +def patched_set_resources_dirty(context): + if not cfg.CONF.QUOTAS.track_quota_usage: + return + + with context.session.begin(subtransactions=True): + for res in res_reg.get_all_resources().values(): + if res_reg.is_tracked(res.name) and res.dirty: + dirty_tenants_snap = res._dirty_tenants.copy() + for tenant_id in dirty_tenants_snap: + query = common_db_api.model_query( + context, quota_models.QuotaUsage) + query = query.filter_by(resource=res.name).filter_by( + tenant_id=tenant_id) + usage_data = query.first() + # Set dirty if not set already. This effectively + # patches the inner notify method: + # https://github.com/openstack/neutron/blob/newton-eol/ + # neutron/api/v2/base.py#L481 + # to avoid updating the QuotaUsages table outside + # from that method (which starts a new transaction). + # The dirty marking would have been already done + # in the ml2plus manager at the end of the pre_commit + # stage (and prior to the plugin initiated transaction + # completing). + if usage_data and not usage_data.dirty: + res.mark_dirty(context) + + +quota.resource_registry.set_resources_dirty = patched_set_resources_dirty + + from neutron._i18n import _LI from oslo_db.sqlalchemy import exc_filters diff --git a/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py b/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py index 0a4ce63db..75858529c 100644 --- a/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py +++ b/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py @@ -44,6 +44,7 @@ from neutron.tests.unit.db import test_db_base_plugin_v2 as test_plugin from neutron.tests.unit.extensions import test_address_scope from neutron.tests.unit.extensions import test_l3 from neutron.tests.unit.extensions import test_securitygroup +from neutron.tests.unit.plugins.ml2 import test_tracked_resources as tr_res from neutron.tests.unit import testlib_api from neutron_lib import constants as n_constants from neutron_lib import context as n_context @@ -238,7 +239,8 @@ class ApicAimTestCase(test_address_scope.AddressScopeTestCase, test_l3.L3NatTestCaseMixin, ApicAimTestMixin, test_securitygroup.SecurityGroupsTestCase): - def setUp(self, mechanism_drivers=None, tenant_network_types=None): + def setUp(self, mechanism_drivers=None, tenant_network_types=None, + plugin=None, ext_mgr=None): # Enable the test mechanism driver to ensure that # we can successfully call through to all mechanism # driver apis. @@ -3061,6 +3063,15 @@ class TestAimMapping(ApicAimTestCase): dhcp_agt_mock.stop() +class TestTrackedResources(tr_res.TestTrackedResources, ApicAimTestCase): + + def setUp(self, **kwargs): + super(TestTrackedResources, self).setUp(**kwargs) + for patch in mock.mock._patch._active_patches: + if patch.attribute == '_ensure_default_security_group': + patch.stop() + + class TestSyncState(ApicAimTestCase): @staticmethod def _get_synced_status(self, context, resource, **kwargs):