From 809e434d2da99cb3e1a778be9838b1175e785e76 Mon Sep 17 00:00:00 2001 From: Pritesh Kothari Date: Wed, 25 Mar 2015 11:34:05 -0700 Subject: [PATCH] Moving VLAN Transparency support from core to extension * Moving VLAN Transparency support from core to extension * Remove the older unit tests and add new corresponding ones DocImpact Closes-Bug: #1434667 Change-Id: Ic551475ed7b64aad9627a57abb0df41acc19bfc1 --- neutron/api/v2/attributes.py | 3 - neutron/db/db_base_plugin_v2.py | 14 ++- neutron/db/vlantransparent_db.py | 29 +++++ neutron/extensions/vlantransparent.py | 75 +++++++++++++ neutron/plugins/ml2/common/exceptions.py | 5 - .../plugins/ml2/drivers/mech_linuxbridge.py | 4 + .../plugins/ml2/drivers/mech_openvswitch.py | 4 + neutron/plugins/ml2/managers.py | 16 ++- neutron/plugins/ml2/plugin.py | 6 +- neutron/tests/retargetable/client_fixtures.py | 1 - neutron/tests/unit/api/v2/test_base.py | 5 +- .../tests/unit/db/test_db_base_plugin_v2.py | 6 - .../tests/unit/extensions/test_providernet.py | 1 - .../unit/extensions/test_vlantransparent.py | 105 ++++++++++++++++++ 14 files changed, 245 insertions(+), 29 deletions(-) create mode 100644 neutron/db/vlantransparent_db.py create mode 100644 neutron/extensions/vlantransparent.py create mode 100644 neutron/tests/unit/extensions/test_vlantransparent.py diff --git a/neutron/api/v2/attributes.py b/neutron/api/v2/attributes.py index 357f8e7f9..61f4d87a9 100644 --- a/neutron/api/v2/attributes.py +++ b/neutron/api/v2/attributes.py @@ -707,9 +707,6 @@ RESOURCE_ATTRIBUTE_MAP = { 'validate': {'type:string': TENANT_ID_MAX_LEN}, 'required_by_policy': True, 'is_visible': True}, - 'vlan_transparent': {'allow_post': True, 'allow_put': False, - 'convert_to': convert_to_boolean, - 'default': False, 'is_visible': True}, SHARED: {'allow_post': True, 'allow_put': True, 'default': False, diff --git a/neutron/db/db_base_plugin_v2.py b/neutron/db/db_base_plugin_v2.py index 6455e72b2..d700666ea 100644 --- a/neutron/db/db_base_plugin_v2.py +++ b/neutron/db/db_base_plugin_v2.py @@ -844,9 +844,14 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, 'mtu': network.get('mtu', constants.DEFAULT_NETWORK_MTU), 'status': network['status'], 'shared': network['shared'], - 'vlan_transparent': network['vlan_transparent'], 'subnets': [subnet['id'] for subnet in network['subnets']]} + # TODO(pritesh): Move vlan_transparent to the extension module. + # vlan_transparent here is only added if the vlantransparent + # extension is enabled. + if ('vlan_transparent' in network and network['vlan_transparent'] != + attributes.ATTR_NOT_SPECIFIED): + res['vlan_transparent'] = network['vlan_transparent'] # Call auxiliary extend functions, if any if process_extensions: self._apply_dict_extend_functions( @@ -951,8 +956,13 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, 'admin_state_up': n['admin_state_up'], 'mtu': n.get('mtu', constants.DEFAULT_NETWORK_MTU), 'shared': n['shared'], - 'vlan_transparent': n.get('vlan_transparent', False), 'status': n.get('status', constants.NET_STATUS_ACTIVE)} + # TODO(pritesh): Move vlan_transparent to the extension module. + # vlan_transparent here is only added if the vlantransparent + # extension is enabled. + if ('vlan_transparent' in n and n['vlan_transparent'] != + attributes.ATTR_NOT_SPECIFIED): + args['vlan_transparent'] = n['vlan_transparent'] network = models_v2.Network(**args) context.session.add(network) return self._make_network_dict(network, process_extensions=False) diff --git a/neutron/db/vlantransparent_db.py b/neutron/db/vlantransparent_db.py new file mode 100644 index 000000000..ba024fe6b --- /dev/null +++ b/neutron/db/vlantransparent_db.py @@ -0,0 +1,29 @@ +# Copyright (c) 2015 Cisco Systems, Inc. All rights reserved. +# +# 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. + +from neutron.api.v2 import attributes +from neutron.db import db_base_plugin_v2 +from neutron.extensions import vlantransparent + + +class Vlantransparent_db_mixin(object): + """Mixin class to add vlan transparent methods to db_base_plugin_v2.""" + + def _extend_network_dict_vlan_transparent(self, network_res, network_db): + network_res[vlantransparent.VLANTRANSPARENT] = ( + network_db.vlan_transparent) + return network_res + + db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs( + attributes.NETWORKS, ['_extend_network_dict_vlan_transparent']) diff --git a/neutron/extensions/vlantransparent.py b/neutron/extensions/vlantransparent.py new file mode 100644 index 000000000..4c2d8f980 --- /dev/null +++ b/neutron/extensions/vlantransparent.py @@ -0,0 +1,75 @@ +# Copyright (c) 2015 Cisco Systems, Inc. All rights reserved. +# +# 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. + +from oslo_config import cfg +from oslo_log import log as logging + +from neutron.api.v2 import attributes +from neutron.common import exceptions as nexception +from neutron.i18n import _LI + +LOG = logging.getLogger(__name__) + + +class VlanTransparencyDriverError(nexception.NeutronException): + """Vlan Transparency not supported by all mechanism drivers.""" + message = _("Backend does not support VLAN Transparency.") + + +VLANTRANSPARENT = 'vlan_transparent' +EXTENDED_ATTRIBUTES_2_0 = { + 'networks': { + VLANTRANSPARENT: {'allow_post': True, 'allow_put': False, + 'convert_to': attributes.convert_to_boolean, + 'default': attributes.ATTR_NOT_SPECIFIED, + 'is_visible': True}, + }, +} + + +def disable_extension_by_config(aliases): + if not cfg.CONF.vlan_transparent: + if 'vlan-transparent' in aliases: + aliases.remove('vlan-transparent') + LOG.info(_LI('Disabled vlantransparent extension.')) + + +class Vlantransparent(object): + """Extension class supporting vlan transparent networks.""" + + @classmethod + def get_name(cls): + return "Vlantransparent" + + @classmethod + def get_alias(cls): + return "vlan-transparent" + + @classmethod + def get_description(cls): + return "Provides Vlan Transparent Networks" + + @classmethod + def get_namespace(cls): + return "http://docs.openstack.org/ext/vlantransparent/api/v1.0" + + @classmethod + def get_updated(cls): + return "2015-03-23T09:00:00-00:00" + + def get_extended_resources(self, version): + if version == "2.0": + return EXTENDED_ATTRIBUTES_2_0 + else: + return {} diff --git a/neutron/plugins/ml2/common/exceptions.py b/neutron/plugins/ml2/common/exceptions.py index 41f46944f..ed94b1e1f 100644 --- a/neutron/plugins/ml2/common/exceptions.py +++ b/neutron/plugins/ml2/common/exceptions.py @@ -21,8 +21,3 @@ from neutron.common import exceptions class MechanismDriverError(exceptions.NeutronException): """Mechanism driver call failed.""" message = _("%(method)s failed.") - - -class VlanTransparencyError(exceptions.NeutronException): - """Vlan Transparency not supported by all mechanism drivers.""" - message = _("Backend does not support VLAN Transparency.") diff --git a/neutron/plugins/ml2/drivers/mech_linuxbridge.py b/neutron/plugins/ml2/drivers/mech_linuxbridge.py index 221b2abfa..f69b5da41 100644 --- a/neutron/plugins/ml2/drivers/mech_linuxbridge.py +++ b/neutron/plugins/ml2/drivers/mech_linuxbridge.py @@ -48,3 +48,7 @@ class LinuxbridgeMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase): def get_mappings(self, agent): return agent['configurations'].get('interface_mappings', {}) + + def check_vlan_transparency(self, context): + """Linuxbridge driver vlan transparency support.""" + return True diff --git a/neutron/plugins/ml2/drivers/mech_openvswitch.py b/neutron/plugins/ml2/drivers/mech_openvswitch.py index 668b3832e..13128a246 100644 --- a/neutron/plugins/ml2/drivers/mech_openvswitch.py +++ b/neutron/plugins/ml2/drivers/mech_openvswitch.py @@ -50,3 +50,7 @@ class OpenvswitchMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase): def get_mappings(self, agent): return agent['configurations'].get('bridge_mappings', {}) + + def check_vlan_transparency(self, context): + """Currently Openvswitch driver doesn't support vlan transparency.""" + return False diff --git a/neutron/plugins/ml2/managers.py b/neutron/plugins/ml2/managers.py index df932c66b..3d31e8d9c 100644 --- a/neutron/plugins/ml2/managers.py +++ b/neutron/plugins/ml2/managers.py @@ -22,6 +22,7 @@ from neutron.common import exceptions as exc from neutron.extensions import multiprovidernet as mpnet from neutron.extensions import portbindings from neutron.extensions import providernet as provider +from neutron.extensions import vlantransparent from neutron.i18n import _LE, _LI from neutron.plugins.ml2.common import exceptions as ml2_exc from neutron.plugins.ml2 import db @@ -297,14 +298,17 @@ class MechanismManager(stevedore.named.NamedExtensionManager): """Helper method for checking vlan transparecncy support. :param context: context parameter to pass to each method call - :raises: neutron.plugins.ml2.common.VlanTransparencyError - if any mechanism driver doesn't support vlan transparency. + :raises: neutron.extensions.vlantransparent. + VlanTransparencyDriverError if any mechanism driver doesn't + support vlan transparency. """ - if not cfg.CONF.vlan_transparent: + if context.current['vlan_transparent'] is None: return - for driver in self.ordered_mech_drivers: - if driver.obj.check_vlan_transparency(context) is False: - raise ml2_exc.VlanTransparencyError() + + if context.current['vlan_transparent']: + for driver in self.ordered_mech_drivers: + if not driver.obj.check_vlan_transparency(context): + raise vlantransparent.VlanTransparencyDriverError() def _call_on_drivers(self, method_name, context, continue_on_failure=False): diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index 7866103ef..66d989cae 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -56,12 +56,14 @@ from neutron.db import models_v2 from neutron.db import netmtu_db from neutron.db import quota_db # noqa from neutron.db import securitygroups_rpc_base as sg_db_rpc +from neutron.db import vlantransparent_db from neutron.extensions import allowedaddresspairs as addr_pair from neutron.extensions import extra_dhcp_opt as edo_ext from neutron.extensions import portbindings from neutron.extensions import portsecurity as psec from neutron.extensions import providernet as provider from neutron.extensions import securitygroup as ext_sg +from neutron.extensions import vlantransparent from neutron.i18n import _LE, _LI, _LW from neutron import manager from neutron.openstack.common import uuidutils @@ -90,6 +92,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, sg_db_rpc.SecurityGroupServerRpcMixin, agentschedulers_db.DhcpAgentSchedulerDbMixin, addr_pair_db.AllowedAddressPairsMixin, + vlantransparent_db.Vlantransparent_db_mixin, extradhcpopt_db.ExtraDhcpOptMixin, netmtu_db.Netmtu_db_mixin): @@ -115,7 +118,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, "dhcp_agent_scheduler", "multi-provider", "allowed-address-pairs", "extra_dhcp_opt", "subnet_allocation", - "net-mtu"] + "net-mtu", "vlan-transparent"] @property def supported_extension_aliases(self): @@ -123,6 +126,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, aliases = self._supported_extension_aliases[:] aliases += self.extension_manager.extension_aliases() sg_rpc.disable_security_group_extension_by_config(aliases) + vlantransparent.disable_extension_by_config(aliases) self._aliases = aliases return self._aliases diff --git a/neutron/tests/retargetable/client_fixtures.py b/neutron/tests/retargetable/client_fixtures.py index 75d9091a0..d284ed605 100644 --- a/neutron/tests/retargetable/client_fixtures.py +++ b/neutron/tests/retargetable/client_fixtures.py @@ -94,7 +94,6 @@ class PluginClientFixture(AbstractClientFixture): # Supply defaults that are expected to be set by the api # framwork kwargs.setdefault('admin_state_up', True) - kwargs.setdefault('vlan_transparent', False) kwargs.setdefault('shared', False) data = dict(network=kwargs) result = self.plugin.create_network(self.ctx, data) diff --git a/neutron/tests/unit/api/v2/test_base.py b/neutron/tests/unit/api/v2/test_base.py index a12eaec7a..6630781fe 100644 --- a/neutron/tests/unit/api/v2/test_base.py +++ b/neutron/tests/unit/api/v2/test_base.py @@ -766,7 +766,6 @@ class JSONV2TestCase(APIv2TestBase, testlib_api.WebTestCase): net_id = _uuid() initial_input = {'network': {'name': 'net1', 'tenant_id': _uuid()}} full_input = {'network': {'admin_state_up': True, - 'vlan_transparent': False, 'shared': False}} full_input['network'].update(initial_input['network']) @@ -801,7 +800,6 @@ class JSONV2TestCase(APIv2TestBase, testlib_api.WebTestCase): # tenant_id should be fetched from env initial_input = {'network': {'name': 'net1'}} full_input = {'network': {'admin_state_up': True, - 'vlan_transparent': False, 'shared': False, 'tenant_id': tenant_id}} full_input['network'].update(initial_input['network']) @@ -1421,8 +1419,7 @@ class ExtensionTestCase(base.BaseTestCase): net_id = _uuid() initial_input = {'network': {'name': 'net1', 'tenant_id': _uuid(), 'v2attrs:something_else': "abc"}} - data = {'network': {'admin_state_up': True, 'shared': False, - 'vlan_transparent': False}} + data = {'network': {'admin_state_up': True, 'shared': False}} data['network'].update(initial_input['network']) return_value = {'subnets': [], 'status': "ACTIVE", diff --git a/neutron/tests/unit/db/test_db_base_plugin_v2.py b/neutron/tests/unit/db/test_db_base_plugin_v2.py index 5db1570ef..152209c38 100644 --- a/neutron/tests/unit/db/test_db_base_plugin_v2.py +++ b/neutron/tests/unit/db/test_db_base_plugin_v2.py @@ -2242,12 +2242,6 @@ class TestNetworksV2(NeutronDbPluginV2TestCase): self.assertEqual(ctx_manager.exception.code, webob.exc.HTTPForbidden.code) - def test_create_network_vlan_transparent(self): - name = "vlan_transparent" - cfg.CONF.set_override('vlan_transparent', True) - with self.network(name=name, vlan_transparent=True) as net: - self.assertEqual(net['network']['vlan_transparent'], True) - def test_update_network(self): with self.network() as network: data = {'network': {'name': 'a_brand_new_name'}} diff --git a/neutron/tests/unit/extensions/test_providernet.py b/neutron/tests/unit/extensions/test_providernet.py index b55eb44d9..a1645e21f 100644 --- a/neutron/tests/unit/extensions/test_providernet.py +++ b/neutron/tests/unit/extensions/test_providernet.py @@ -139,7 +139,6 @@ class ProvidernetExtensionTestCase(testlib_api.WebTestCase): exp_input = {'network': data} exp_input['network'].update({'admin_state_up': True, 'tenant_id': 'an_admin', - 'vlan_transparent': False, 'shared': False}) instance.create_network.assert_called_with(mock.ANY, network=exp_input) diff --git a/neutron/tests/unit/extensions/test_vlantransparent.py b/neutron/tests/unit/extensions/test_vlantransparent.py new file mode 100644 index 000000000..38ffbf688 --- /dev/null +++ b/neutron/tests/unit/extensions/test_vlantransparent.py @@ -0,0 +1,105 @@ +# Copyright (c) 2015 Cisco Systems Inc. All rights reserved. +# +# 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. + +from oslo_config import cfg +from webob import exc as web_exc + +from neutron.api.v2 import attributes +from neutron.db import db_base_plugin_v2 +from neutron.db import vlantransparent_db as vlt_db +from neutron.extensions import vlantransparent as vlt +from neutron import quota +from neutron.tests.unit.db import test_db_base_plugin_v2 +from neutron.tests.unit import testlib_api + + +class VlanTransparentExtensionManager(object): + + def get_resources(self): + return [] + + def get_actions(self): + return [] + + def get_request_extensions(self): + return [] + + def get_extended_resources(self, version): + return vlt.get_extended_resources(version) + + +class VlanTransparentExtensionTestPlugin(db_base_plugin_v2.NeutronDbPluginV2, + vlt_db.Vlantransparent_db_mixin): + """Test plugin to mixin the VLAN transparent extensions.""" + + supported_extension_aliases = ["vlan-transparent"] + + +class VlanTransparentExtensionTestCase(test_db_base_plugin_v2.TestNetworksV2): + fmt = 'json' + + def setUp(self): + plugin = ('neutron.tests.unit.extensions.test_vlantransparent.' + 'VlanTransparentExtensionTestPlugin') + + # Save the global RESOURCE_ATTRIBUTE_MAP + self.saved_attr_map = {} + for resource, attrs in attributes.RESOURCE_ATTRIBUTE_MAP.iteritems(): + self.saved_attr_map[resource] = attrs.copy() + + # Update the plugin and extensions path + self.setup_coreplugin(plugin) + cfg.CONF.set_override('allow_pagination', True) + cfg.CONF.set_override('allow_sorting', True) + ext_mgr = VlanTransparentExtensionManager() + self.addCleanup(self._restore_attribute_map) + super(VlanTransparentExtensionTestCase, self).setUp(plugin=plugin, + ext_mgr=ext_mgr) + + quota.QUOTAS._driver = None + cfg.CONF.set_override('quota_driver', 'neutron.quota.ConfDriver', + group='QUOTAS') + + def _restore_attribute_map(self): + # Restore the global RESOURCE_ATTRIBUTE_MAP + attributes.RESOURCE_ATTRIBUTE_MAP = self.saved_attr_map + + def test_network_create_with_vlan_transparent_attr(self): + vlantrans = {'vlan_transparent': True} + with self.network(name='net1', **vlantrans) as net: + req = self.new_show_request('networks', net['network']['id']) + res = self.deserialize(self.fmt, req.get_response(self.api)) + self.assertEqual(net['network']['name'], + res['network']['name']) + self.assertEqual(True, res['network'][vlt.VLANTRANSPARENT]) + + def test_network_create_with_bad_vlan_transparent_attr(self): + vlantrans = {'vlan_transparent': "abc"} + with testlib_api.ExpectedException( + web_exc.HTTPClientError) as ctx_manager: + with self.network(name='net1', **vlantrans): + pass + self.assertEqual(web_exc.HTTPClientError.code, + ctx_manager.exception.code) + + def test_network_update_with_vlan_transparent_exception(self): + with self.network(name='net1') as net: + self._update('networks', net['network']['id'], + {'network': {vlt.VLANTRANSPARENT: False}}, + web_exc.HTTPBadRequest.code) + req = self.new_show_request('networks', net['network']['id']) + res = self.deserialize(self.fmt, req.get_response(self.api)) + self.assertEqual(net['network']['name'], + res['network']['name']) + self.assertEqual(None, res['network'][vlt.VLANTRANSPARENT]) -- 2.45.2