Merge "Add scaffolding for trunk plugin/server-side driver integration"
This commit is contained in:
commit
6f61b3d77c
|
@ -0,0 +1,27 @@
|
|||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# 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.drivers.openvswitch import driver as ovs_driver
|
||||
|
||||
|
||||
def register():
|
||||
"""Load in-tree drivers for the service plugin."""
|
||||
# Enable the trunk plugin to work with ML2/OVS. Support for other
|
||||
# drivers can be added similarly by executing the registration
|
||||
# code at the time of plugin/mech driver initialization. There should
|
||||
# be at least one compatible driver enabled in the deployment for trunk
|
||||
# setup to be successful. The plugin fails to initialize if no compatible
|
||||
# driver is found in the deployment.
|
||||
ovs_driver.register()
|
|
@ -0,0 +1,70 @@
|
|||
# Copyright 2016 Hewlett Packard Enterprise Development Company, LP
|
||||
#
|
||||
# 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 abc
|
||||
|
||||
from neutron.callbacks import events
|
||||
from neutron.callbacks import registry
|
||||
from neutron.services.trunk import constants as trunk_consts
|
||||
|
||||
|
||||
class DriverBase(object):
|
||||
|
||||
def __init__(self, name, interfaces, segmentation_types,
|
||||
agent_type=None, can_trunk_bound_port=False):
|
||||
"""Instantiate a trunk driver.
|
||||
|
||||
:param name: driver name.
|
||||
:param interfaces: list of interfaces supported.
|
||||
:param segmentation_types: list of segmentation types supported.
|
||||
:param agent_type: agent type for the driver, None if agentless.
|
||||
:param can_trunk_bound_port: True if trunk creation is allowed
|
||||
for a bound parent port (i.e. trunk creation after VM boot).
|
||||
"""
|
||||
|
||||
self.name = name
|
||||
self.interfaces = interfaces
|
||||
self.segmentation_types = segmentation_types
|
||||
self.agent_type = agent_type
|
||||
self.can_trunk_bound_port = can_trunk_bound_port
|
||||
registry.subscribe(self.register,
|
||||
trunk_consts.TRUNK_PLUGIN,
|
||||
events.AFTER_INIT)
|
||||
|
||||
@abc.abstractproperty
|
||||
def is_loaded(self):
|
||||
"""True if the driver is active for the Neutron Server.
|
||||
|
||||
Implement this property to determine if your driver is actively
|
||||
configured for this Neutron Server deployment, e.g. check if
|
||||
core_plugin or mech_drivers config options (for ML2) is set as
|
||||
required.
|
||||
"""
|
||||
|
||||
def register(self, resource, event, trigger, **kwargs):
|
||||
"""Register the trunk driver.
|
||||
|
||||
This method should be overriden so that the driver can subscribe
|
||||
to the required trunk events. The driver should also advertise
|
||||
itself as supported driver by calling register_driver() on the
|
||||
TrunkPlugin otherwise the trunk plugin may fail to start if no
|
||||
compatible configuration is found.
|
||||
|
||||
External drivers must subscribe to the AFTER_INIT event for the
|
||||
trunk plugin so that they can integrate without an explicit
|
||||
register() method invocation.
|
||||
"""
|
||||
# Advertise yourself!
|
||||
trigger.register_driver(self)
|
|
@ -0,0 +1,56 @@
|
|||
# Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
#
|
||||
# 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 import constants
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from neutron.extensions import portbindings
|
||||
from neutron.services.trunk import constants as trunk_consts
|
||||
from neutron.services.trunk.drivers import base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
NAME = 'openvswitch'
|
||||
|
||||
SUPPORTED_INTERFACES = (
|
||||
portbindings.VIF_TYPE_OVS,
|
||||
portbindings.VIF_TYPE_VHOST_USER,
|
||||
)
|
||||
|
||||
SUPPORTED_SEGMENTATION_TYPES = (
|
||||
trunk_consts.VLAN,
|
||||
)
|
||||
|
||||
DRIVER = None
|
||||
|
||||
|
||||
class OVSDriver(base.DriverBase):
|
||||
|
||||
@property
|
||||
def is_loaded(self):
|
||||
return NAME in cfg.CONF.ml2.mechanism_drivers
|
||||
|
||||
@classmethod
|
||||
def create(cls):
|
||||
return OVSDriver(NAME,
|
||||
SUPPORTED_INTERFACES,
|
||||
SUPPORTED_SEGMENTATION_TYPES,
|
||||
constants.AGENT_TYPE_OVS)
|
||||
|
||||
|
||||
def register():
|
||||
"""Register the driver."""
|
||||
global DRIVER
|
||||
DRIVER = OVSDriver.create()
|
||||
LOG.debug('Open vSwitch trunk driver registered')
|
|
@ -62,3 +62,8 @@ class TrunkDisabled(n_exc.Conflict):
|
|||
class TrunkInErrorState(n_exc.Conflict):
|
||||
message = _("Trunk %(trunk_id)s is in error state. Attempt "
|
||||
"to resolve the error condition before proceeding.")
|
||||
|
||||
|
||||
class IncompatibleTrunkPluginConfiguration(n_exc.NeutronException):
|
||||
message = _("Cannot load trunk plugin: no compatible core plugin "
|
||||
"configuration is found.")
|
||||
|
|
|
@ -30,6 +30,7 @@ from neutron.objects import trunk as trunk_objects
|
|||
from neutron.services import service_base
|
||||
from neutron.services.trunk import callbacks
|
||||
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
|
||||
|
||||
|
@ -61,11 +62,39 @@ class TrunkPlugin(service_base.ServicePluginBase,
|
|||
def __init__(self):
|
||||
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
|
||||
attributes.PORTS, [_extend_port_trunk_details])
|
||||
self._drivers = []
|
||||
self._segmentation_types = {}
|
||||
self._interfaces = set()
|
||||
self._agent_types = set()
|
||||
drivers.register()
|
||||
registry.subscribe(rules.enforce_port_deletion_rules,
|
||||
resources.PORT, events.BEFORE_DELETE)
|
||||
registry.notify(constants.TRUNK_PLUGIN, events.AFTER_INIT, self)
|
||||
LOG.debug('Trunk plugin loaded')
|
||||
for driver in self._drivers:
|
||||
LOG.debug('Trunk plugin loaded with driver %s', driver.name)
|
||||
self.check_compatibility()
|
||||
|
||||
def check_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 register_driver(self, driver):
|
||||
"""Register driver with trunk plugin."""
|
||||
if driver.agent_type:
|
||||
self._agent_types.add(driver.agent_type)
|
||||
self._interfaces = self._interfaces | set(driver.interfaces)
|
||||
self._drivers.append(driver)
|
||||
|
||||
@property
|
||||
def supported_interfaces(self):
|
||||
"""A set of supported interfaces."""
|
||||
return self._interfaces
|
||||
|
||||
@property
|
||||
def supported_agent_types(self):
|
||||
"""A set of supported agent types."""
|
||||
return self._agent_types
|
||||
|
||||
def add_segmentation_type(self, segmentation_type, id_validator):
|
||||
self._segmentation_types[segmentation_type] = id_validator
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
# Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
#
|
||||
# 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 import constants
|
||||
from oslo_config import cfg
|
||||
|
||||
from neutron.services.trunk.drivers.openvswitch import driver
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
class OVSDriverTestCase(base.BaseTestCase):
|
||||
|
||||
def test_driver_creation(self):
|
||||
ovs_driver = driver.OVSDriver.create()
|
||||
self.assertFalse(ovs_driver.is_loaded)
|
||||
self.assertEqual(driver.NAME, ovs_driver.name)
|
||||
self.assertEqual(driver.SUPPORTED_INTERFACES, ovs_driver.interfaces)
|
||||
self.assertEqual(driver.SUPPORTED_SEGMENTATION_TYPES,
|
||||
ovs_driver.segmentation_types)
|
||||
self.assertEqual(constants.AGENT_TYPE_OVS, ovs_driver.agent_type)
|
||||
self.assertFalse(ovs_driver.can_trunk_bound_port)
|
||||
|
||||
def test_driver_is_loaded(self):
|
||||
cfg.CONF.set_override('mechanism_drivers',
|
||||
'openvswitch', group='ml2')
|
||||
ovs_driver = driver.OVSDriver.create()
|
||||
self.assertTrue(ovs_driver.is_loaded)
|
|
@ -15,12 +15,16 @@
|
|||
|
||||
import mock
|
||||
|
||||
import testtools
|
||||
|
||||
from neutron.callbacks import events
|
||||
from neutron.callbacks import registry
|
||||
from neutron import manager
|
||||
from neutron.objects import trunk as trunk_objects
|
||||
from neutron.services.trunk import callbacks
|
||||
from neutron.services.trunk import constants
|
||||
from neutron.services.trunk import drivers
|
||||
from neutron.services.trunk.drivers import base
|
||||
from neutron.services.trunk import exceptions as trunk_exc
|
||||
from neutron.services.trunk import plugin as trunk_plugin
|
||||
from neutron.tests.unit.plugins.ml2 import test_plugin
|
||||
|
@ -42,6 +46,9 @@ class TrunkPluginTestCase(test_plugin.Ml2PluginV2TestCase):
|
|||
|
||||
def setUp(self):
|
||||
super(TrunkPluginTestCase, self).setUp()
|
||||
self.drivers_patch = mock.patch.object(drivers, 'register').start()
|
||||
self.compat_patch = mock.patch.object(
|
||||
trunk_plugin.TrunkPlugin, 'check_compatibility').start()
|
||||
self.trunk_plugin = trunk_plugin.TrunkPlugin()
|
||||
self.trunk_plugin.add_segmentation_type('vlan', lambda x: True)
|
||||
|
||||
|
@ -258,3 +265,33 @@ class TrunkPluginTestCase(test_plugin.Ml2PluginV2TestCase):
|
|||
self.context, trunk['id'],
|
||||
{'sub_ports': [{'port_id': subport['port']['id']}]})
|
||||
self.assertEqual(constants.PENDING_STATUS, trunk['status'])
|
||||
|
||||
|
||||
class FakeDriver(base.DriverBase):
|
||||
|
||||
@property
|
||||
def is_loaded(self):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def create(cls):
|
||||
return FakeDriver('foo_name', ('foo_intfs',), ('foo_seg_types',))
|
||||
|
||||
|
||||
class TrunkPluginDriversTestCase(test_plugin.Ml2PluginV2TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TrunkPluginDriversTestCase, self).setUp()
|
||||
mock.patch.object(drivers, 'register').start()
|
||||
|
||||
def test_plugin_fails_to_start(self):
|
||||
with testtools.ExpectedException(
|
||||
trunk_exc.IncompatibleTrunkPluginConfiguration):
|
||||
trunk_plugin.TrunkPlugin()
|
||||
|
||||
def test_plugin_with_fake_driver(self):
|
||||
fake_driver = 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)
|
||||
|
|
|
@ -22,6 +22,7 @@ from oslo_utils import uuidutils
|
|||
from neutron import manager
|
||||
from neutron.plugins.common import utils
|
||||
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 import rules
|
||||
|
@ -119,6 +120,9 @@ class TrunkPortValidatorTestCase(test_plugin.Ml2PluginV2TestCase):
|
|||
|
||||
def setUp(self):
|
||||
super(TrunkPortValidatorTestCase, self).setUp()
|
||||
self.drivers_patch = mock.patch.object(drivers, 'register').start()
|
||||
self.compat_patch = mock.patch.object(
|
||||
trunk_plugin.TrunkPlugin, 'check_compatibility').start()
|
||||
self.trunk_plugin = trunk_plugin.TrunkPlugin()
|
||||
self.trunk_plugin.add_segmentation_type(constants.VLAN,
|
||||
utils.is_valid_vlan_tag)
|
||||
|
|
Loading…
Reference in New Issue