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
This commit is contained in:
Armando Migliaccio 2016-08-16 10:49:24 -07:00
parent ab324cf161
commit fb5c043d0d
11 changed files with 153 additions and 77 deletions

View File

@ -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

View File

@ -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.")

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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')

View File

@ -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):

View File

@ -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')

View File

@ -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,18 +268,35 @@ 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):
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)