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:
parent
ab324cf161
commit
fb5c043d0d
|
@ -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
|
|
|
@ -69,6 +69,18 @@ class IncompatibleTrunkPluginConfiguration(n_exc.NeutronException):
|
||||||
"configuration is found.")
|
"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):
|
class TrunkPluginDriverConflict(n_exc.Conflict):
|
||||||
message = _("A misconfiguration in the environment prevents the "
|
message = _("A misconfiguration in the environment prevents the "
|
||||||
"operation from completing, please, contact the admin.")
|
"operation from completing, please, contact the admin.")
|
||||||
|
|
|
@ -33,6 +33,7 @@ from neutron.services.trunk import constants
|
||||||
from neutron.services.trunk import drivers
|
from neutron.services.trunk import drivers
|
||||||
from neutron.services.trunk import exceptions as trunk_exc
|
from neutron.services.trunk import exceptions as trunk_exc
|
||||||
from neutron.services.trunk import rules
|
from neutron.services.trunk import rules
|
||||||
|
from neutron.services.trunk.seg_types import validators
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -76,10 +77,44 @@ class TrunkPlugin(service_base.ServicePluginBase,
|
||||||
self.check_compatibility()
|
self.check_compatibility()
|
||||||
|
|
||||||
def check_compatibility(self):
|
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."""
|
"""Fail to load if no compatible driver is found."""
|
||||||
if not any([driver.is_loaded for driver in self._drivers]):
|
if not any([driver.is_loaded for driver in self._drivers]):
|
||||||
raise trunk_exc.IncompatibleTrunkPluginConfiguration()
|
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):
|
def set_rpc_backend(self, backend):
|
||||||
self._rpc_backend = backend
|
self._rpc_backend = backend
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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()
|
|
|
@ -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')
|
|
|
@ -33,7 +33,7 @@ class FakeDriver2(base.DriverBase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls):
|
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):
|
class FakeDriverCanTrunkBoundPort(base.DriverBase):
|
||||||
|
|
|
@ -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')
|
|
@ -26,6 +26,7 @@ from neutron.services.trunk import constants
|
||||||
from neutron.services.trunk import drivers
|
from neutron.services.trunk import drivers
|
||||||
from neutron.services.trunk import exceptions as trunk_exc
|
from neutron.services.trunk import exceptions as trunk_exc
|
||||||
from neutron.services.trunk import plugin as trunk_plugin
|
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.plugins.ml2 import test_plugin
|
||||||
from neutron.tests.unit.services.trunk import fakes
|
from neutron.tests.unit.services.trunk import fakes
|
||||||
|
|
||||||
|
@ -267,18 +268,35 @@ class TrunkPluginTestCase(test_plugin.Ml2PluginV2TestCase):
|
||||||
self.assertEqual(constants.DOWN_STATUS, trunk['status'])
|
self.assertEqual(constants.DOWN_STATUS, trunk['status'])
|
||||||
|
|
||||||
|
|
||||||
class TrunkPluginDriversTestCase(test_plugin.Ml2PluginV2TestCase):
|
class TrunkPluginCompatDriversTestCase(test_plugin.Ml2PluginV2TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TrunkPluginDriversTestCase, self).setUp()
|
super(TrunkPluginCompatDriversTestCase, self).setUp()
|
||||||
mock.patch.object(drivers, 'register').start()
|
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(
|
with testtools.ExpectedException(
|
||||||
trunk_exc.IncompatibleTrunkPluginConfiguration):
|
trunk_exc.IncompatibleTrunkPluginConfiguration):
|
||||||
trunk_plugin.TrunkPlugin()
|
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):
|
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()
|
fake_driver = fakes.FakeDriver.create()
|
||||||
plugin = trunk_plugin.TrunkPlugin()
|
plugin = trunk_plugin.TrunkPlugin()
|
||||||
self.assertTrue(fake_driver.is_loaded)
|
self.assertTrue(fake_driver.is_loaded)
|
||||||
|
|
Loading…
Reference in New Issue