diff --git a/neutron/conf/common.py b/neutron/conf/common.py index bbe9d61b98f..be221bb27ec 100644 --- a/neutron/conf/common.py +++ b/neutron/conf/common.py @@ -128,6 +128,10 @@ core_opts = [ cfg.BoolOpt('vlan_transparent', default=False, help=_('If True, then allow plugins that support it to ' 'create VLAN transparent networks.')), + cfg.BoolOpt('vlan_qinq', default=False, + help=_('If True, then allow plugins that support it to ' + 'create VLAN transparent networks using 0x8a88 ' + 'ethertype.')), cfg.BoolOpt('filter_validation', default=True, help=_('If True, then allow plugins to decide ' 'whether to perform validations on filter parameters. ' diff --git a/neutron/db/migration/alembic_migrations/versions/2025.1/expand/ad80a9f07c5c_add_vlan_qinq_column_to_the_network_.py b/neutron/db/migration/alembic_migrations/versions/2025.1/expand/ad80a9f07c5c_add_vlan_qinq_column_to_the_network_.py new file mode 100644 index 00000000000..349c0aa7f2a --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/2025.1/expand/ad80a9f07c5c_add_vlan_qinq_column_to_the_network_.py @@ -0,0 +1,35 @@ +# Copyright 2024 OpenStack Foundation +# +# 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 alembic import op +import sqlalchemy as sa + + +# Add qinq column to the Network table +# +# Revision ID: ad80a9f07c5c +# Revises: 5bcb7b31ec7d +# Create Date: 2024-12-09 11:27:41.108660 + +# revision identifiers, used by Alembic. +revision = 'ad80a9f07c5c' +down_revision = '5bcb7b31ec7d' + + +def upgrade(): + op.add_column( + 'networks', + sa.Column('qinq', sa.Boolean(), server_default=None) + ) diff --git a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD index c8434b3c3ad..2665ab596fe 100644 --- a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD +++ b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD @@ -1 +1 @@ -5bcb7b31ec7d +ad80a9f07c5c diff --git a/neutron/db/models_v2.py b/neutron/db/models_v2.py index 943e566dc24..e9cdf6cee6a 100644 --- a/neutron/db/models_v2.py +++ b/neutron/db/models_v2.py @@ -321,6 +321,7 @@ class Network(standard_attr.HasStandardAttributes, model_base.BASEV2, status = sa.Column(sa.String(16)) admin_state_up = sa.Column(sa.Boolean) vlan_transparent = sa.Column(sa.Boolean, nullable=True) + qinq = sa.Column(sa.Boolean, nullable=True) rbac_entries = orm.relationship(rbac_db_models.NetworkRBAC, backref=orm.backref('network', load_on_pending=True), diff --git a/neutron/db/qinq_db.py b/neutron/db/qinq_db.py new file mode 100644 index 00000000000..4c0cb4c0904 --- /dev/null +++ b/neutron/db/qinq_db.py @@ -0,0 +1,27 @@ +# Copyright (c) 2024 Red Hat, Inc. +# +# 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_lib.api.definitions import network as net_def +from neutron_lib.api.definitions import qinq as qinq_def +from neutron_lib.db import resource_extend + + +@resource_extend.has_resource_extenders +class Vlanqinq_db_mixin: + """Mixin class to add vlan QinQ methods to db_base_plugin_v2.""" + + @staticmethod + @resource_extend.extends([net_def.COLLECTION_NAME]) + def _extend_network_dict_vlan_qinq(network_res, network_db): + network_res[qinq_def.QINQ_FIELD] = network_db.qinq + return network_res diff --git a/neutron/extensions/qinq.py b/neutron/extensions/qinq.py new file mode 100644 index 00000000000..0c7b5dd5418 --- /dev/null +++ b/neutron/extensions/qinq.py @@ -0,0 +1,48 @@ +# Copyright (c) 2024 Red Hat, Inc. +# +# 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_lib.api.definitions import qinq as apidef +from neutron_lib.api import extensions as api_extensions +from neutron_lib.api import validators +from oslo_config import cfg +from oslo_log import log as logging + +LOG = logging.getLogger(__name__) + + +def _disable_extension_by_config(aliases): + if not cfg.CONF.vlan_qinq: + if apidef.ALIAS in aliases: + aliases.remove(apidef.ALIAS) + LOG.info('Disabled VLAN QinQ extension.') + + +def get_qinq(network): + """Get the value of vlan_qinq from a network if set. + + :param network: The network dict to retrieve the value of vlan_qinq + from. + :returns: The value of vlan_qinq from the network dict if set in + the dict, otherwise False is returned. + """ + return (network[apidef.QINQ_FIELD] + if (apidef.QINQ_FIELD in network and + validators.is_attr_set(network[apidef.QINQ_FIELD])) + else False) + + +class Qinq(api_extensions.APIExtensionDescriptor): + """Extension class supporting vlan QinQ networks.""" + + api_definition = apidef diff --git a/neutron/objects/network.py b/neutron/objects/network.py index 0f2f5a6e6af..1e472a85aa8 100644 --- a/neutron/objects/network.py +++ b/neutron/objects/network.py @@ -200,7 +200,8 @@ class ExternalNetwork(base.NeutronDbObject): class Network(rbac_db.NeutronRbacObject): # Version 1.0: Initial version # Version 1.1: Changed 'mtu' to be not nullable - VERSION = '1.1' + # Version 1.2: Added 'qinq' field + VERSION = '1.2' rbac_db_cls = NetworkRBAC db_model = models_v2.Network @@ -212,6 +213,7 @@ class Network(rbac_db.NeutronRbacObject): 'status': obj_fields.StringField(nullable=True), 'admin_state_up': obj_fields.BooleanField(nullable=True), 'vlan_transparent': obj_fields.BooleanField(nullable=True), + 'qinq': obj_fields.BooleanField(nullable=True), # TODO(ihrachys): consider converting to a field of stricter type 'availability_zone_hints': obj_fields.ListOfStringsField( nullable=True), @@ -337,6 +339,8 @@ class Network(rbac_db.NeutronRbacObject): # mtu will not be nullable after raise exception.IncompatibleObjectVersion( objver=target_version, objname=self.__class__.__name__) + if _target_version < (1, 2): + primitive.pop('qinq', None) @base.NeutronObjectRegistry.register diff --git a/neutron/plugins/ml2/drivers/l2pop/mech_driver.py b/neutron/plugins/ml2/drivers/l2pop/mech_driver.py index 5447cf60d10..262e960ef28 100644 --- a/neutron/plugins/ml2/drivers/l2pop/mech_driver.py +++ b/neutron/plugins/ml2/drivers/l2pop/mech_driver.py @@ -60,6 +60,10 @@ class L2populationMechanismDriver(api.MechanismDriver): """L2population driver vlan transparency support.""" return True + def check_vlan_qinq(self, context): + """L2population driver doesn't support vlan transparency.""" + return False + def _get_ha_port_agents_fdb( self, context, network_id, router_id): other_fdb_ports = {} diff --git a/neutron/plugins/ml2/drivers/macvtap/mech_driver/mech_macvtap.py b/neutron/plugins/ml2/drivers/macvtap/mech_driver/mech_macvtap.py index 88c160f9ff5..f52ef12e048 100644 --- a/neutron/plugins/ml2/drivers/macvtap/mech_driver/mech_macvtap.py +++ b/neutron/plugins/ml2/drivers/macvtap/mech_driver/mech_macvtap.py @@ -60,6 +60,10 @@ class MacvtapMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase): """Macvtap driver vlan transparency support.""" return False + def check_vlan_qinq(self, context): + """Currently Macvtap driver doesn't support QinQ vlan.""" + return False + def _is_live_migration(self, context): # We cannot just check if # context.original['host_id'] != context.current['host_id'] diff --git a/neutron/plugins/ml2/drivers/mech_sriov/mech_driver/mech_driver.py b/neutron/plugins/ml2/drivers/mech_sriov/mech_driver/mech_driver.py index ae6bd2db8e3..ca471b35c04 100644 --- a/neutron/plugins/ml2/drivers/mech_sriov/mech_driver/mech_driver.py +++ b/neutron/plugins/ml2/drivers/mech_sriov/mech_driver/mech_driver.py @@ -201,6 +201,10 @@ class SriovNicSwitchMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase): """SR-IOV driver vlan transparency support.""" return True + def check_vlan_qinq(self, context): + """Currently SR-IOV driver doesn't support QinQ vlan.""" + return False + def _get_vif_details(self, segment): network_type = segment[api.NETWORK_TYPE] if network_type == constants.TYPE_FLAT: diff --git a/neutron/plugins/ml2/drivers/openvswitch/mech_driver/mech_openvswitch.py b/neutron/plugins/ml2/drivers/openvswitch/mech_driver/mech_openvswitch.py index 91f9a318181..f5b1dab582a 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/mech_driver/mech_openvswitch.py +++ b/neutron/plugins/ml2/drivers/openvswitch/mech_driver/mech_openvswitch.py @@ -113,6 +113,10 @@ class OpenvswitchMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase): """Currently Openvswitch driver doesn't support vlan transparency.""" return False + def check_vlan_qinq(self, context): + """Currently Openvswitch driver doesn't support QinQ vlan.""" + return False + def bind_port(self, context): vnic_type = context.current.get(portbindings.VNIC_TYPE, portbindings.VNIC_NORMAL) diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py index 36e1a4d2692..eac8d984f5b 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py @@ -225,6 +225,10 @@ class OVNMechanismDriver(api.MechanismDriver): return (context.current.get(provider_net.NETWORK_TYPE) in vlan_transparency_network_types) + def check_vlan_qinq(self, context): + """OVN driver vlan QinQ support.""" + return False + def _setup_vif_port_bindings(self): self.supported_vnic_types = ovn_const.OVN_SUPPORTED_VNIC_TYPES self.vif_details = { diff --git a/neutron/plugins/ml2/managers.py b/neutron/plugins/ml2/managers.py index 9d38cca16e2..0fd18256cd5 100644 --- a/neutron/plugins/ml2/managers.py +++ b/neutron/plugins/ml2/managers.py @@ -23,6 +23,7 @@ from neutron_lib.db import api as db_api from neutron_lib import exceptions as exc from neutron_lib.exceptions import multiprovidernet as mpnet_exc from neutron_lib.exceptions import placement as place_exc +from neutron_lib.exceptions import vlanqinq as qinq_exc from neutron_lib.exceptions import vlantransparent as vlan_exc from neutron_lib.plugins.ml2 import api from oslo_config import cfg @@ -469,6 +470,19 @@ class MechanismManager(stevedore.named.NamedExtensionManager): if not driver.obj.check_vlan_transparency(context): raise vlan_exc.VlanTransparencyDriverError() + def _check_vlan_qinq(self, context): + """Helper method for checking vlan qinq support. + + :param context: context parameter to pass to each method call + :raises: neutron_lib.exceptions.qinq. + VlanQinqDriverError if any mechanism driver doesn't + support vlan transparency. + """ + if context.current.get('qinq'): + for driver in self.ordered_mech_drivers: + if not driver.obj.check_vlan_qinq(context): + raise qinq_exc.VlanQinqDriverError() + def start_driver_rpc_listeners(self): servers = [] for driver in self.ordered_mech_drivers: @@ -529,6 +543,7 @@ class MechanismManager(stevedore.named.NamedExtensionManager): that all mechanism drivers are called in this case. """ self._check_vlan_transparency(context) + self._check_vlan_qinq(context) self._call_on_drivers("create_network_precommit", context, raise_db_retriable=True) diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index a18b3c66f1b..90b7692114e 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -52,6 +52,7 @@ from neutron_lib.api.definitions import port_security as psec from neutron_lib.api.definitions import portbindings from neutron_lib.api.definitions import portbindings_extended as pbe_ext from neutron_lib.api.definitions import provider_net +from neutron_lib.api.definitions import qinq as qinq_apidef from neutron_lib.api.definitions import quota_check_limit from neutron_lib.api.definitions import rbac_address_groups as rbac_ag_apidef from neutron_lib.api.definitions import rbac_address_scope @@ -128,12 +129,14 @@ from neutron.db import extradhcpopt_db from neutron.db.models import securitygroup as sg_models from neutron.db import models_v2 from neutron.db import provisioning_blocks +from neutron.db import qinq_db from neutron.db import securitygroups_rpc_base as sg_db_rpc from neutron.db import segments_db from neutron.db import subnet_service_type_mixin from neutron.db import vlantransparent_db from neutron.extensions import dhcpagentscheduler as dhcp_ext from neutron.extensions import filter_validation +from neutron.extensions import qinq from neutron.extensions import quota_check_limit_default from neutron.extensions import security_groups_default_rules as \ sg_default_rules_ext @@ -186,7 +189,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, extradhcpopt_db.ExtraDhcpOptMixin, address_scope_db.AddressScopeDbMixin, subnet_service_type_mixin.SubnetServiceTypeMixin, - address_group_db.AddressGroupDbMixin): + address_group_db.AddressGroupDbMixin, + qinq_db.Vlanqinq_db_mixin): """Implement the Neutron L2 abstractions using modules. @@ -253,6 +257,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, sg_default_rules_ext.ALIAS, sg_rules_default_sg.ALIAS, subnet_ext_net_def.ALIAS, + qinq_apidef.ALIAS, ] # List of agent types for which all binding_failed ports should try to be @@ -268,6 +273,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, vlantransparent._disable_extension_by_config(aliases) filter_validation._disable_extension_by_config(aliases) dhcp_ext.disable_extension_by_config(aliases) + qinq._disable_extension_by_config(aliases) self._aliases = self._filter_extensions_by_mech_driver(aliases) return self._aliases @@ -1212,10 +1218,23 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, self.type_manager.extend_network_dict_provider(context, result) # Update the transparent vlan if configured + is_vlan_transparent = None if extensions.is_extension_supported(self, 'vlan-transparent'): - vlt = vlan_apidef.get_vlan_transparent(net_data) - net_db['vlan_transparent'] = vlt - result['vlan_transparent'] = vlt + is_vlan_transparent = vlan_apidef.get_vlan_transparent( + net_data) + net_db['vlan_transparent'] = is_vlan_transparent + result['vlan_transparent'] = is_vlan_transparent + # Update the vlan QinQ if configured + qinq_value = None + if extensions.is_extension_supported(self, qinq_apidef.ALIAS): + qinq_value = qinq.get_qinq(net_data) + net_db['qinq'] = qinq_value + result['qinq'] = qinq_value + # QinQ and vlan_transparent can't be both set to True + if is_vlan_transparent and qinq_value: + msg = _("Attributes 'vlan_transparent' and 'qinq' can not be " + "set to True for the same network.") + raise exc.BadRequest(resource='network', msg=msg) az_hints = utils.get_az_hints(net_data) if az_hints: self.validate_availability_zones(context, 'network', az_hints) diff --git a/neutron/tests/unit/extensions/test_qinq.py b/neutron/tests/unit/extensions/test_qinq.py new file mode 100644 index 00000000000..753710d1816 --- /dev/null +++ b/neutron/tests/unit/extensions/test_qinq.py @@ -0,0 +1,132 @@ +# Copyright (c) 2024 Red Hat, Inc. +# +# 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_lib.api.definitions import provider_net +from neutron_lib.api.definitions import qinq as qinq_apidef +from neutron_lib.api.definitions import vlantransparent as vlan_apidef +from oslo_config import cfg +from webob import exc as web_exc + +from neutron.db import qinq_db +from neutron.plugins.ml2 import plugin as ml2_plugin +from neutron.tests.common import test_db_base_plugin_v2 +from neutron.tests.unit import testlib_api + + +class QinqExtensionTestPlugin(ml2_plugin.Ml2Plugin, + qinq_db.Vlanqinq_db_mixin): + """Test plugin to mixin the VLAN transparent extensions.""" + + supported_extension_aliases = [provider_net.ALIAS, + qinq_apidef.ALIAS, + vlan_apidef.ALIAS] + + +class QinqExtensionTestCase(test_db_base_plugin_v2.TestNetworksV2): + fmt = 'json' + + def setUp(self): + plugin = ('neutron.tests.unit.extensions.test_qinq.' + 'QinqExtensionTestPlugin') + + cfg.CONF.set_override('network_vlan_ranges', 'datacentre', + group='ml2_type_vlan') + super().setUp(plugin=plugin) + + def test_create_network_with_qinq_attr(self): + arg_list = ( + qinq_apidef.QINQ_FIELD), + net_kwargs = { + qinq_apidef.QINQ_FIELD: True + } + with self.network(name='net1', as_admin=True, + arg_list=arg_list, **net_kwargs) 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.assertTrue(res['network'][qinq_apidef.QINQ_FIELD]) + + def test_create_network_with_bad_qinq_attr(self): + arg_list = ( + qinq_apidef.QINQ_FIELD), + net_kwargs = { + qinq_apidef.QINQ_FIELD: 'this is not boolean value', + } + with testlib_api.ExpectedException( + web_exc.HTTPClientError) as ctx_manager: + with self.network(name='net1', as_admin=True, + arg_list=arg_list, **net_kwargs): + pass + self.assertEqual(web_exc.HTTPClientError.code, + ctx_manager.exception.code) + + def test_network_update_with_qinq_exception(self): + arg_list = ( + qinq_apidef.QINQ_FIELD), + net_kwargs = { + qinq_apidef.QINQ_FIELD: False, + } + with self.network(name='net1', as_admin=True, + arg_list=arg_list, **net_kwargs) as net: + self._update('networks', net['network']['id'], + {'network': {qinq_apidef.QINQ_FIELD: True}}, + 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.assertFalse(res['network'][qinq_apidef.QINQ_FIELD]) + + def _test_create_network_qinq_and_transparent_vlan(self, qinq_value, vlt): + arg_list = ( + qinq_apidef.QINQ_FIELD, + vlan_apidef.VLANTRANSPARENT) + net_kwargs = { + qinq_apidef.QINQ_FIELD: qinq_value, + vlan_apidef.VLANTRANSPARENT: vlt, + } + # Both vlan_transparent and qinq can't be set for the same network + if qinq_value and vlt: + with testlib_api.ExpectedException( + web_exc.HTTPClientError) as ctx_manager: + with self.network(name='net1', as_admin=True, + arg_list=arg_list, **net_kwargs): + pass + self.assertEqual(web_exc.HTTPBadRequest.code, + ctx_manager.exception.code) + return + + # In any other case it should work fine + with self.network(name='net1', as_admin=True, + arg_list=arg_list, **net_kwargs) 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(qinq_value, + res['network'][qinq_apidef.QINQ_FIELD]) + self.assertEqual(vlt, res['network'][vlan_apidef.VLANTRANSPARENT]) + + def test_create_network_qinq_disabled_transparent_vlan_enabled(self): + self._test_create_network_qinq_and_transparent_vlan(False, True) + + def test_create_network_qinq_disabled_transparent_vlan_disabled(self): + self._test_create_network_qinq_and_transparent_vlan(False, False) + + def test_create_network_qinq_enabled_transparent_vlan_disabled(self): + self._test_create_network_qinq_and_transparent_vlan(True, False) + + def test_create_network_qinq_enabled_transparent_vlan_enabled(self): + self._test_create_network_qinq_and_transparent_vlan(True, True) diff --git a/neutron/tests/unit/objects/test_network.py b/neutron/tests/unit/objects/test_network.py index 865a417de26..d2a01c165e9 100644 --- a/neutron/tests/unit/objects/test_network.py +++ b/neutron/tests/unit/objects/test_network.py @@ -260,6 +260,11 @@ class NetworkDbObjectTestCase(obj_test_base.BaseDbObjectTestCase, obj = network.Network.get_object(self.context, id=obj.id) self.assertEqual('bar.com', obj.dns_domain) + def test_v1_2_to_v1_1_drops_qinq_attribute(self): + network_obj = self._make_object(self.obj_fields[0]) + network_v1_1 = network_obj.obj_to_primitive(target_version='1.1') + self.assertNotIn('qinq', network_v1_1['versioned_object.data']) + class SegmentHostMappingIfaceObjectTestCase( obj_test_base.BaseObjectIfaceTestCase): diff --git a/neutron/tests/unit/objects/test_objects.py b/neutron/tests/unit/objects/test_objects.py index 701263af174..6b392d590e7 100644 --- a/neutron/tests/unit/objects/test_objects.py +++ b/neutron/tests/unit/objects/test_objects.py @@ -65,7 +65,7 @@ object_data = { 'MeteringLabelRule': '2.0-0ad09894c62e1ce6e868f725158959ba', 'Log': '1.0-6391351c0f34ed34375a19202f361d24', 'NDPProxy': '1.0-a6597d9caac3bb0d63f943f82e4dda8c', - 'Network': '1.1-c3e9ecc0618ee934181d91b143a48901', + 'Network': '1.2-0221c921b40f11b237e6a274984f238a', 'NetworkDhcpAgentBinding': '1.1-d9443c88809ffa4c45a0a5a48134b54a', 'NetworkDNSDomain': '1.0-420db7910294608534c1e2e30d6d8319', 'NetworkPortSecurity': '1.0-b30802391a87945ee9c07582b4ff95e3', diff --git a/neutron/tests/unit/plugins/ml2/drivers/mechanism_logger.py b/neutron/tests/unit/plugins/ml2/drivers/mechanism_logger.py index 6ac6cf38ce0..ca08f2f1932 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/mechanism_logger.py +++ b/neutron/tests/unit/plugins/ml2/drivers/mechanism_logger.py @@ -96,6 +96,11 @@ class LoggerMechanismDriver(api.MechanismDriver): self._log_diff_call("check_vlan_transparency", context) return True + def check_vlan_qinq(self, context): + self._log_network_call("check_vlan_qinq", context) + self._log_diff_call("check_vlan_qinq", context) + return True + def _log_subnet_call(self, method_name, context): LOG.info("%(method)s called with subnet settings %(current)s " "(original settings %(original)s)",