Add scaffolding for trunk plugin/server-side driver integration
This patch introduces a framework and enhancements that allow the trunk plugin to support multiple trunk drivers that integrate with Neutron core technologies. A mechanism to validate the deployment configuration is provided so that the trunk plugin can verify that it can start correctly. Fail fast is desired to give prompt feedback to the operator in case of configuration inconsistencies. The OVS driver is developed as a proof of concept and the Linux Bridge driver will be added as a follow up patch. Partially-implements: blueprint vlan-aware-vms Change-Id: I5f9f8845e4108754748be3b64cce250c06e20f1f
This commit is contained in:
parent
a96b84e1c1
commit
46b6e129ff
|
@ -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