From fb5c043d0d8854b743352addbc902aeb83949345 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Tue, 16 Aug 2016 10:49:24 -0700 Subject: [PATCH] Revisit support for trunk segmentation types This patch improves how the trunk plugin supports future segmentation types and how it can handle multi-driver deployments where each driver may have its own set of supported segmentation types. It does so by: a) removing the existing segmentation type registration mechanism; this has become superseded by how drivers advertise their capabilities and register themselves with the plugin. b) introducing a new compatibility check that makes the plugin fails to load in case of conflicts: for multi-drivers environments a common set of segmentation types must be found for the plugin to load successfully. In case of single driver deployments, a driver can bring its own segmentation type. Partially-implement: blueprint vlan-aware-vms Change-Id: I0bd8b479c07945c65e14d4b59dfbcc1bc9a85a88 --- neutron/services/trunk/__init__.py | 15 ------- neutron/services/trunk/exceptions.py | 12 ++++++ neutron/services/trunk/plugin.py | 35 ++++++++++++++++ neutron/services/trunk/seg_types/__init__.py | 0 .../services/trunk/seg_types/validators.py | 41 +++++++++++++++++++ neutron/services/trunk/validators/__init__.py | 18 -------- neutron/services/trunk/validators/vlan.py | 34 --------------- neutron/tests/unit/services/trunk/fakes.py | 2 +- .../unit/services/trunk/seg_types/__init__.py | 0 .../trunk/seg_types/test_validators.py | 37 +++++++++++++++++ .../tests/unit/services/trunk/test_plugin.py | 36 ++++++++++++---- 11 files changed, 153 insertions(+), 77 deletions(-) create mode 100644 neutron/services/trunk/seg_types/__init__.py create mode 100644 neutron/services/trunk/seg_types/validators.py delete mode 100644 neutron/services/trunk/validators/__init__.py delete mode 100644 neutron/services/trunk/validators/vlan.py create mode 100644 neutron/tests/unit/services/trunk/seg_types/__init__.py create mode 100644 neutron/tests/unit/services/trunk/seg_types/test_validators.py diff --git a/neutron/services/trunk/__init__.py b/neutron/services/trunk/__init__.py index 616ade0b3bc..e69de29bb2d 100644 --- a/neutron/services/trunk/__init__.py +++ b/neutron/services/trunk/__init__.py @@ -1,15 +0,0 @@ -# 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. - -import neutron.services.trunk.validators # noqa diff --git a/neutron/services/trunk/exceptions.py b/neutron/services/trunk/exceptions.py index ddeb6448930..604162085ce 100644 --- a/neutron/services/trunk/exceptions.py +++ b/neutron/services/trunk/exceptions.py @@ -69,6 +69,18 @@ class IncompatibleTrunkPluginConfiguration(n_exc.NeutronException): "configuration is found.") +class IncompatibleDriverSegmentationTypes(n_exc.NeutronException): + message = _("Cannot load trunk plugin: no compatible segmentation " + "type configuration can be found amongst list of loaded " + "drivers.") + + +class SegmentationTypeValidatorNotFound(n_exc.NotFound): + message = _("Validator not found for segmentation type %(seg_type)s. " + "It must be registered before the plugin init can " + "proceed.") + + class TrunkPluginDriverConflict(n_exc.Conflict): message = _("A misconfiguration in the environment prevents the " "operation from completing, please, contact the admin.") diff --git a/neutron/services/trunk/plugin.py b/neutron/services/trunk/plugin.py index 8a01f545479..58646b9c8a2 100644 --- a/neutron/services/trunk/plugin.py +++ b/neutron/services/trunk/plugin.py @@ -33,6 +33,7 @@ from neutron.services.trunk import constants from neutron.services.trunk import drivers from neutron.services.trunk import exceptions as trunk_exc from neutron.services.trunk import rules +from neutron.services.trunk.seg_types import validators LOG = logging.getLogger(__name__) @@ -76,10 +77,44 @@ class TrunkPlugin(service_base.ServicePluginBase, self.check_compatibility() def check_compatibility(self): + """Verify the plugin can load correctly and fail otherwise.""" + self.check_driver_compatibility() + self.check_segmentation_compatibility() + + def check_driver_compatibility(self): """Fail to load if no compatible driver is found.""" if not any([driver.is_loaded for driver in self._drivers]): raise trunk_exc.IncompatibleTrunkPluginConfiguration() + def check_segmentation_compatibility(self): + """Fail to load if segmentation type conflicts are found. + + In multi-driver deployments each loaded driver must support the same + set of segmentation types consistently. + """ + # Get list of segmentation types for the loaded drivers. + list_of_driver_seg_types = [ + set(driver.segmentation_types) for driver in self._drivers + if driver.is_loaded + ] + + # If not empty, check that there is at least one we can use. + compat_segmentation_types = set() + if list_of_driver_seg_types: + compat_segmentation_types = ( + set.intersection(*list_of_driver_seg_types)) + if not compat_segmentation_types: + raise trunk_exc.IncompatibleDriverSegmentationTypes() + + # If there is at least one, make sure the validator is defined. + try: + for seg_type in compat_segmentation_types: + self.add_segmentation_type( + seg_type, validators.get_validator(seg_type)) + except KeyError: + raise trunk_exc.SegmentationTypeValidatorNotFound( + seg_type=seg_type) + def set_rpc_backend(self, backend): self._rpc_backend = backend diff --git a/neutron/services/trunk/seg_types/__init__.py b/neutron/services/trunk/seg_types/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/services/trunk/seg_types/validators.py b/neutron/services/trunk/seg_types/validators.py new file mode 100644 index 00000000000..4ae2d900ee9 --- /dev/null +++ b/neutron/services/trunk/seg_types/validators.py @@ -0,0 +1,41 @@ +# 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._i18n import _ + +from neutron.plugins.common import utils +from neutron.services.trunk import constants as trunk_consts + +# Base map of segmentation types supported with their respective validator +# functions. In multi-driver deployments all drivers must support the same +# set of segmentation types consistently. Drivers can add their own type +# and respective validator, however this is a configuration that may be +# supported only in single-driver deployments. +_supported = { + trunk_consts.VLAN: utils.is_valid_vlan_tag, +} + + +def get_validator(segmentation_type): + """Get validator for the segmentation type or KeyError if not found.""" + return _supported[segmentation_type] + + +def add_validator(segmentation_type, validator_function): + """Introduce new entry to the map of supported segmentation types.""" + if segmentation_type in _supported: + msg = _("Cannot redefine existing %s " + "segmentation type") % segmentation_type + raise KeyError(msg) + _supported[segmentation_type] = validator_function diff --git a/neutron/services/trunk/validators/__init__.py b/neutron/services/trunk/validators/__init__.py deleted file mode 100644 index 5ef4e49c021..00000000000 --- a/neutron/services/trunk/validators/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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.services.trunk.validators import vlan - -# Register segmentation_type validation drivers -vlan.register() diff --git a/neutron/services/trunk/validators/vlan.py b/neutron/services/trunk/validators/vlan.py deleted file mode 100644 index a9ef8f89570..00000000000 --- a/neutron/services/trunk/validators/vlan.py +++ /dev/null @@ -1,34 +0,0 @@ -# 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_log import log as logging - -from neutron.callbacks import events -from neutron.callbacks import registry -from neutron.plugins.common import utils -from neutron.services.trunk import constants as trunk_consts - -LOG = logging.getLogger(__name__) - - -def register(): - registry.subscribe(handler, trunk_consts.TRUNK_PLUGIN, - events.AFTER_INIT) - LOG.debug('Registering for trunk support') - - -def handler(resource, event, trigger, **kwargs): - trigger.add_segmentation_type( - trunk_consts.VLAN, utils.is_valid_vlan_tag) - LOG.debug('Registration complete') diff --git a/neutron/tests/unit/services/trunk/fakes.py b/neutron/tests/unit/services/trunk/fakes.py index 3dd93a1aff1..55ef2991b4d 100644 --- a/neutron/tests/unit/services/trunk/fakes.py +++ b/neutron/tests/unit/services/trunk/fakes.py @@ -33,7 +33,7 @@ class FakeDriver2(base.DriverBase): @classmethod def create(cls): - return cls('foo_name2', ('foo_intf2',), ('foo_seg_types',)) + return cls('foo_name2', ('foo_intf2',), ('foo_seg_types2',)) class FakeDriverCanTrunkBoundPort(base.DriverBase): diff --git a/neutron/tests/unit/services/trunk/seg_types/__init__.py b/neutron/tests/unit/services/trunk/seg_types/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/tests/unit/services/trunk/seg_types/test_validators.py b/neutron/tests/unit/services/trunk/seg_types/test_validators.py new file mode 100644 index 00000000000..8f2ef1c564d --- /dev/null +++ b/neutron/tests/unit/services/trunk/seg_types/test_validators.py @@ -0,0 +1,37 @@ +# 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 mock + +from neutron.services.trunk import constants +from neutron.services.trunk.seg_types import validators +from neutron.tests import base + + +class ValidatorsTestCase(base.BaseTestCase): + + def test_add_validator_raises_keyerror_on_redefinition(self): + self.assertRaises(KeyError, + validators.add_validator, + constants.VLAN, mock.ANY) + + def test_add_validator_add_new_type(self): + validators.add_validator('foo', lambda: None) + self.assertIn('foo', validators._supported) + + def test_get_validator(self): + self.assertIsNotNone(validators.get_validator(constants.VLAN)) + + def test_get_validator_raises_keyerror_on_missing_validator(self): + self.assertRaises(KeyError, + validators.get_validator, 'my_random_seg_type') diff --git a/neutron/tests/unit/services/trunk/test_plugin.py b/neutron/tests/unit/services/trunk/test_plugin.py index 31cda4103ef..c4bcf8c9613 100644 --- a/neutron/tests/unit/services/trunk/test_plugin.py +++ b/neutron/tests/unit/services/trunk/test_plugin.py @@ -26,6 +26,7 @@ from neutron.services.trunk import constants from neutron.services.trunk import drivers from neutron.services.trunk import exceptions as trunk_exc from neutron.services.trunk import plugin as trunk_plugin +from neutron.services.trunk.seg_types import validators from neutron.tests.unit.plugins.ml2 import test_plugin from neutron.tests.unit.services.trunk import fakes @@ -267,21 +268,38 @@ class TrunkPluginTestCase(test_plugin.Ml2PluginV2TestCase): self.assertEqual(constants.DOWN_STATUS, trunk['status']) -class TrunkPluginDriversTestCase(test_plugin.Ml2PluginV2TestCase): +class TrunkPluginCompatDriversTestCase(test_plugin.Ml2PluginV2TestCase): def setUp(self): - super(TrunkPluginDriversTestCase, self).setUp() + super(TrunkPluginCompatDriversTestCase, self).setUp() mock.patch.object(drivers, 'register').start() - def test_plugin_fails_to_start(self): + def test_plugin_fails_to_start_no_loaded_drivers(self): with testtools.ExpectedException( trunk_exc.IncompatibleTrunkPluginConfiguration): trunk_plugin.TrunkPlugin() + def test_plugins_fails_to_start_seg_type_validator_not_found(self): + fakes.FakeDriver.create() + with mock.patch.object( + validators, 'get_validator', side_effect=KeyError), \ + testtools.ExpectedException( + trunk_exc.SegmentationTypeValidatorNotFound): + trunk_plugin.TrunkPlugin() + + def test_plugins_fails_to_start_conflicting_seg_types(self): + fakes.FakeDriver.create() + fakes.FakeDriver2.create() + with testtools.ExpectedException( + trunk_exc.IncompatibleDriverSegmentationTypes): + trunk_plugin.TrunkPlugin() + def test_plugin_with_fake_driver(self): - fake_driver = fakes.FakeDriver.create() - plugin = trunk_plugin.TrunkPlugin() - self.assertTrue(fake_driver.is_loaded) - self.assertEqual(set([]), plugin.supported_agent_types) - self.assertEqual(set(['foo_intfs']), plugin.supported_interfaces) - self.assertEqual([fake_driver], plugin.registered_drivers) + with mock.patch.object(validators, 'get_validator', + return_value={'foo_seg_types': mock.ANY}): + fake_driver = fakes.FakeDriver.create() + plugin = trunk_plugin.TrunkPlugin() + self.assertTrue(fake_driver.is_loaded) + self.assertEqual(set([]), plugin.supported_agent_types) + self.assertEqual(set(['foo_intfs']), plugin.supported_interfaces) + self.assertEqual([fake_driver], plugin.registered_drivers)