From 194dd33947f1939ab9e1976ccc465c1878086bcf Mon Sep 17 00:00:00 2001 From: Thomas Bachman Date: Tue, 8 Aug 2017 18:49:25 +0000 Subject: [PATCH] Fix per-project quotas for L3 plugin The GBP L3 plugin is using the old quotas driver. This prevents per-project quotas for L3 resources. The L3 plugin should be changed to support the new qutoas driver. Change-Id: Idbc651a905ca5c567527ba2094a83b2d0486ca9a Closes-Bug: 1709456 --- .../neutron/plugins/ml2plus/patch_neutron.py | 1 + .../neutron/services/apic_aim/l3_plugin.py | 4 + .../unit/plugins/ml2plus/test_l3_apic_aim.py | 48 +++++++++++ .../plugins/ml2plus/test_tracked_resources.py | 85 +++++++++++++++++++ 4 files changed, 138 insertions(+) create mode 100644 gbpservice/neutron/tests/unit/plugins/ml2plus/test_tracked_resources.py diff --git a/gbpservice/neutron/plugins/ml2plus/patch_neutron.py b/gbpservice/neutron/plugins/ml2plus/patch_neutron.py index e85b7f1e9..3b2238126 100644 --- a/gbpservice/neutron/plugins/ml2plus/patch_neutron.py +++ b/gbpservice/neutron/plugins/ml2plus/patch_neutron.py @@ -171,6 +171,7 @@ def undecorated(o): from neutron.db.quota import api as quota_api +from neutron.db.quota import driver # noqa from neutron import quota diff --git a/gbpservice/neutron/services/apic_aim/l3_plugin.py b/gbpservice/neutron/services/apic_aim/l3_plugin.py index bff6a2efa..afd08176c 100644 --- a/gbpservice/neutron/services/apic_aim/l3_plugin.py +++ b/gbpservice/neutron/services/apic_aim/l3_plugin.py @@ -18,9 +18,11 @@ from neutron.db import common_db_mixin from neutron.db import db_base_plugin_v2 from neutron.db import dns_db from neutron.db import extraroute_db +from neutron.db import l3_db from neutron.db import l3_gwmode_db from neutron.extensions import l3 from neutron.extensions import portbindings +from neutron.quota import resource_registry from neutron_lib import constants from neutron_lib import exceptions from oslo_log import log as logging @@ -56,6 +58,8 @@ class ApicL3Plugin(common_db_mixin.CommonDbMixin, def get_plugin_description(): return _("L3 Router Service Plugin using the APIC via AIM") + @resource_registry.tracked_resources(router=l3_db.Router, + floatingip=l3_db.FloatingIP) def __init__(self): LOG.info(_LI("APIC AIM L3 Plugin __init__")) extensions.append_api_extensions_path(extensions_pkg.__path__) diff --git a/gbpservice/neutron/tests/unit/plugins/ml2plus/test_l3_apic_aim.py b/gbpservice/neutron/tests/unit/plugins/ml2plus/test_l3_apic_aim.py index cf6454d3b..4291c9cb4 100644 --- a/gbpservice/neutron/tests/unit/plugins/ml2plus/test_l3_apic_aim.py +++ b/gbpservice/neutron/tests/unit/plugins/ml2plus/test_l3_apic_aim.py @@ -35,8 +35,31 @@ class TestCiscoApicAimL3Plugin(test_aim_mapping_driver.AIMBaseTestCase): class so that it can inherit test infrastructure from those classes. ''' + def _verify_event_handler_calls(self, data, expected_call_count=1): + if not hasattr(data, '__iter__') or isinstance(data, dict): + data = [data] + self.assertEqual(expected_call_count, self.handler_mock.call_count) + call_idx = -1 + for item in data: + if item: + model = self.handler_mock.call_args_list[call_idx][0][-1] + self.assertEqual(model['id'], item['id']) + self.assertEqual(model['tenant_id'], item['tenant_id']) + call_idx = call_idx - 1 + def setUp(self): + handler_patch = mock.patch( + 'neutron.quota.resource.TrackedResource._db_event_handler') + self.handler_mock = handler_patch.start() + def_sec_group_patch = mock.patch( + 'neutron.db.securitygroups_db.SecurityGroupDbMixin.' + '_ensure_default_security_group') + def_sec_group_patch.start() super(TestCiscoApicAimL3Plugin, self).setUp() + get_sec_group_port_patch = mock.patch( + 'neutron.db.securitygroups_db.SecurityGroupDbMixin.' + '_get_security_groups_on_port') + get_sec_group_port_patch.start() # Set up L2 objects for L3 test attr = {'tenant_id': TENANT} @@ -93,3 +116,28 @@ class TestCiscoApicAimL3Plugin(test_aim_mapping_driver.AIMBaseTestCase): def test_remove_router_interface_port(self): self._test_remove_router_interface(self.interface_info['port']) + + def test_create_delete_floating_ip_triggers_event(self): + net = self._make_network('json', 'meh', True) + subnet = self._make_subnet('json', net, '14.0.0.1', + '14.0.0.0/24')['subnet'] + self._set_net_external(subnet['network_id']) + kwargs = {'floatingip': {'tenant_id': TENANT, + 'subnet_id': subnet['id'], + 'floating_network_id': subnet['network_id']}} + self.handler_mock.reset_mock() + floatingip = self.plugin.create_floatingip(self.context, kwargs) + internal_ports = self._show('ports', + floatingip['port_id'])['ports'] + for port in internal_ports: + if port['device_owner'] == 'network:floatingip' and ( + port['device_id'] == floatingip['id']): + internal_port = port + # When a floatingip is created it also creates port, therefore + # there will be four calls in total to the event handler + self._verify_event_handler_calls(floatingip, + expected_call_count=2) + self._delete('floatingips', floatingip['id']) + # Expecting 2 more calls - 1 for the port, 1 for the floatingip + self._verify_event_handler_calls( + [internal_port, floatingip], expected_call_count=4) diff --git a/gbpservice/neutron/tests/unit/plugins/ml2plus/test_tracked_resources.py b/gbpservice/neutron/tests/unit/plugins/ml2plus/test_tracked_resources.py new file mode 100644 index 000000000..ec9af4951 --- /dev/null +++ b/gbpservice/neutron/tests/unit/plugins/ml2plus/test_tracked_resources.py @@ -0,0 +1,85 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import functools +import mock + +from neutron.api import extensions +from neutron.tests.unit.plugins.ml2 import ( + test_tracked_resources as n_tracked) +from neutron.tests.unit.plugins.ml2 import test_plugin + +from gbpservice.neutron.db import implicitsubnetpool_db # noqa +import gbpservice.neutron.extensions + +PLUGIN_NAME = 'ml2plus' + + +class Ml2PlusConfFixture(test_plugin.PluginConfFixture): + + def __init__(self, parent_setup=None): + super(Ml2PlusConfFixture, self).__init__(PLUGIN_NAME, parent_setup) + + +class Ml2PlusTestTrackedResourcesEventHandler( + n_tracked.TestTrackedResourcesEventHandler): + + def setUp(self): + extensions.append_api_extensions_path( + gbpservice.neutron.extensions.__path__) + # Prevent noise from default security group operations + super(Ml2PlusTestTrackedResourcesEventHandler, self).setUp() + get_sec_group_port_patch = mock.patch( + 'neutron.db.securitygroups_db.SecurityGroupDbMixin.' + '_get_security_groups_on_port') + get_sec_group_port_patch.start() + + def setup_parent(self): + """Perform parent setup with the common plugin configuration class.""" + service_plugins = {'l3_plugin_name': self.l3_plugin} + service_plugins.update(self.get_additional_service_plugins()) + # Ensure that the parent setup can be called without arguments + # by the common configuration setUp. + parent_setup = functools.partial( + super(test_plugin.Ml2PluginV2TestCase, self).setUp, + plugin=PLUGIN_NAME, + service_plugins=service_plugins, + ) + self.useFixture(Ml2PlusConfFixture(parent_setup)) + self.port_create_status = 'DOWN' + + +class Ml2PlusTestTrackedResources(n_tracked.TestTrackedResources): + + def setUp(self): + extensions.append_api_extensions_path( + gbpservice.neutron.extensions.__path__) + # Prevent noise from default security group operations + super(Ml2PlusTestTrackedResources, self).setUp() + get_sec_group_port_patch = mock.patch( + 'neutron.db.securitygroups_db.SecurityGroupDbMixin.' + '_get_security_groups_on_port') + get_sec_group_port_patch.start() + + def setup_parent(self): + """Perform parent setup with the common plugin configuration class.""" + service_plugins = {'l3_plugin_name': self.l3_plugin} + service_plugins.update(self.get_additional_service_plugins()) + # Ensure that the parent setup can be called without arguments + # by the common configuration setUp. + parent_setup = functools.partial( + super(test_plugin.Ml2PluginV2TestCase, self).setUp, + plugin=PLUGIN_NAME, + service_plugins=service_plugins, + ) + self.useFixture(Ml2PlusConfFixture(parent_setup)) + self.port_create_status = 'DOWN'