Merge "Add scaffolding for trunk plugin/server-side driver integration"

This commit is contained in:
Jenkins 2016-08-09 17:45:21 +00:00 committed by Gerrit Code Review
commit 6f61b3d77c
11 changed files with 266 additions and 1 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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