# Example: mechanism_drivers = openvswitch,brocade
# Example: mechanism_drivers = linuxbridge,brocade
+# (ListOpt) Ordered list of extension driver entrypoints
+# to be loaded from the neutron.ml2.extension_drivers namespace.
+# extension_drivers =
+# Example: extension_drivers = anewextensiondriver
+
[ml2_type_flat]
# (ListOpt) List of physical_network names with which flat networks
# can be created. Use * to allow flat networks with arbitrary
def append_api_extensions_path(paths):
- paths = [cfg.CONF.api_extensions_path] + paths
+ paths = list(set([cfg.CONF.api_extensions_path] + paths))
cfg.CONF.set_override('api_extensions_path',
':'.join([p for p in paths if p]))
for route in subnet['routes']],
'shared': subnet['shared']
}
+ # Call auxiliary extend functions, if any
+ self._apply_dict_extend_functions(attributes.SUBNETS, res, subnet)
return self._fields(res, fields)
def _make_port_dict(self, port, fields=None,
help=_("An ordered list of networking mechanism driver "
"entrypoints to be loaded from the "
"neutron.ml2.mechanism_drivers namespace.")),
+ cfg.ListOpt('extension_drivers',
+ default=[],
+ help=_("An ordered list of extension driver "
+ "entrypoints to be loaded from the "
+ "neutron.ml2.extension_drivers namespace.")),
]
that such state changes are eventually cleaned up.
"""
pass
+
+
+@six.add_metaclass(abc.ABCMeta)
+class ExtensionDriver(object):
+ """Define stable abstract interface for ML2 extension drivers.
+
+ An extension driver extends the core resources implemented by the
+ ML2 plugin with additional attributes. Methods that process create
+ and update operations for these resources validate and persist
+ values for extended attributes supplied through the API. Other
+ methods extend the resource dictionaries returned from the API
+ operations with the values of the extended attributes.
+ """
+
+ @abc.abstractmethod
+ def initialize(self):
+ """Perform driver initialization.
+
+ Called after all drivers have been loaded and the database has
+ been initialized. No abstract methods defined below will be
+ called prior to this method being called.
+ """
+ pass
+
+ @abc.abstractproperty
+ def extension_alias(self):
+ """Supported extension alias.
+
+ Return the alias identifying the core API extension supported
+ by this driver.
+ """
+ pass
+
+ def process_create_network(self, session, data, result):
+ """Process extended attributes for create network.
+
+ :param session: database session
+ :param data: dictionary of incoming network data
+ :param result: network dictionary to extend
+
+ Called inside transaction context on session to validate and
+ persist any extended network attributes defined by this
+ driver. Extended attribute values must also be added to
+ result.
+ """
+ pass
+
+ def process_create_subnet(self, session, data, result):
+ """Process extended attributes for create subnet.
+
+ :param session: database session
+ :param data: dictionary of incoming subnet data
+ :param result: subnet dictionary to extend
+
+ Called inside transaction context on session to validate and
+ persist any extended subnet attributes defined by this
+ driver. Extended attribute values must also be added to
+ result.
+ """
+ pass
+
+ def process_create_port(self, session, data, result):
+ """Process extended attributes for create port.
+
+ :param session: database session
+ :param data: dictionary of incoming port data
+ :param result: port dictionary to extend
+
+ Called inside transaction context on session to validate and
+ persist any extended port attributes defined by this
+ driver. Extended attribute values must also be added to
+ result.
+ """
+ pass
+
+ def process_update_network(self, session, data, result):
+ """Process extended attributes for update network.
+
+ :param session: database session
+ :param data: dictionary of incoming network data
+ :param result: network dictionary to extend
+
+ Called inside transaction context on session to validate and
+ update any extended network attributes defined by this
+ driver. Extended attribute values, whether updated or not,
+ must also be added to result.
+ """
+ pass
+
+ def process_update_subnet(self, session, data, result):
+ """Process extended attributes for update subnet.
+
+ :param session: database session
+ :param data: dictionary of incoming subnet data
+ :param result: subnet dictionary to extend
+
+ Called inside transaction context on session to validate and
+ update any extended subnet attributes defined by this
+ driver. Extended attribute values, whether updated or not,
+ must also be added to result.
+ """
+ pass
+
+ def process_update_port(self, session, data, result):
+ """Process extended attributes for update port.
+
+ :param session: database session
+ :param data: dictionary of incoming port data
+ :param result: port dictionary to extend
+
+ Called inside transaction context on session to validate and
+ update any extended port attributes defined by this
+ driver. Extended attribute values, whether updated or not,
+ must also be added to result.
+ """
+ pass
+
+ def extend_network_dict(self, session, result):
+ """Add extended attributes to network dictionary.
+
+ :param session: database session
+ :param result: network dictionary to extend
+
+ Called inside transaction context on session to add any
+ extended attributes defined by this driver to a network
+ dictionary to be used for mechanism driver calls and/or
+ returned as the result of a network operation.
+ """
+ pass
+
+ def extend_subnet_dict(self, session, result):
+ """Add extended attributes to subnet dictionary.
+
+ :param session: database session
+ :param result: subnet dictionary to extend
+
+ Called inside transaction context on session to add any
+ extended attributes defined by this driver to a subnet
+ dictionary to be used for mechanism driver calls and/or
+ returned as the result of a subnet operation.
+ """
+ pass
+
+ def extend_port_dict(self, session, result):
+ """Add extended attributes to port dictionary.
+
+ :param session: database session
+ :param result: port dictionary to extend
+
+ Called inside transaction context on session to add any
+ extended attributes defined by this driver to a port
+ dictionary to be used for mechanism driver calls and/or
+ returned as the result of a port operation.
+ """
+ pass
LOG.warning(_("Failed to bind port %(port)s on host %(host)s"),
{'port': context._port['id'],
'host': binding.host})
+
+
+class ExtensionManager(stevedore.named.NamedExtensionManager):
+ """Manage extension drivers using drivers."""
+
+ def __init__(self):
+ # Ordered list of extension drivers, defining
+ # the order in which the drivers are called.
+ self.ordered_ext_drivers = []
+
+ LOG.info(_("Configured extension driver names: %s"),
+ cfg.CONF.ml2.extension_drivers)
+ super(ExtensionManager, self).__init__('neutron.ml2.extension_drivers',
+ cfg.CONF.ml2.extension_drivers,
+ invoke_on_load=True,
+ name_order=True)
+ LOG.info(_("Loaded extension driver names: %s"), self.names())
+ self._register_drivers()
+
+ def _register_drivers(self):
+ """Register all extension drivers.
+
+ This method should only be called once in the ExtensionManager
+ constructor.
+ """
+ for ext in self:
+ self.ordered_ext_drivers.append(ext)
+ LOG.info(_("Registered extension drivers: %s"),
+ [driver.name for driver in self.ordered_ext_drivers])
+
+ def initialize(self):
+ # Initialize each driver in the list.
+ for driver in self.ordered_ext_drivers:
+ LOG.info(_("Initializing extension driver '%s'"), driver.name)
+ driver.obj.initialize()
+
+ def extension_aliases(self):
+ exts = []
+ for driver in self.ordered_ext_drivers:
+ alias = driver.obj.extension_alias
+ exts.append(alias)
+ LOG.info(_("Got %(alias)s extension from driver '%(drv)s'"),
+ {'alias': alias, 'drv': driver.name})
+ return exts
+
+ def _call_on_ext_drivers(self, method_name, session, data, result):
+ """Helper method for calling a method across all extension drivers."""
+ for driver in self.ordered_ext_drivers:
+ try:
+ getattr(driver.obj, method_name)(session, data, result)
+ except Exception:
+ LOG.exception(
+ _("Extension driver '%(name)s' failed in %(method)s"),
+ {'name': driver.name, 'method': method_name}
+ )
+
+ def process_create_network(self, session, data, result):
+ """Notify all extension drivers during network creation."""
+ self._call_on_ext_drivers("process_create_network", session, data,
+ result)
+
+ def process_update_network(self, session, data, result):
+ """Notify all extension drivers during network update."""
+ self._call_on_ext_drivers("process_update_network", session, data,
+ result)
+
+ def process_create_subnet(self, session, data, result):
+ """Notify all extension drivers during subnet creation."""
+ self._call_on_ext_drivers("process_create_subnet", session, data,
+ result)
+
+ def process_update_subnet(self, session, data, result):
+ """Notify all extension drivers during subnet update."""
+ self._call_on_ext_drivers("process_update_subnet", session, data,
+ result)
+
+ def process_create_port(self, session, data, result):
+ """Notify all extension drivers during port creation."""
+ self._call_on_ext_drivers("process_create_port", session, data, result)
+
+ def process_update_port(self, session, data, result):
+ """Notify all extension drivers during port update."""
+ self._call_on_ext_drivers("process_update_port", session, data, result)
+
+ def extend_network_dict(self, session, result):
+ """Notify all extension drivers to extend network dictionary."""
+ for driver in self.ordered_ext_drivers:
+ driver.obj.extend_network_dict(session, result)
+ LOG.info(_("Extended network dict for driver '%(drv)s'"),
+ {'drv': driver.name})
+
+ def extend_subnet_dict(self, session, result):
+ """Notify all extension drivers to extend subnet dictionary."""
+ for driver in self.ordered_ext_drivers:
+ driver.obj.extend_subnet_dict(session, result)
+ LOG.info(_("Extended subnet dict for driver '%(drv)s'"),
+ {'drv': driver.name})
+
+ def extend_port_dict(self, session, result):
+ """Notify all extension drivers to extend port dictionary."""
+ for driver in self.ordered_ext_drivers:
+ driver.obj.extend_port_dict(session, result)
+ LOG.info(_("Extended port dict for driver '%(drv)s'"),
+ {'drv': driver.name})
from neutron.db import agents_db
from neutron.db import agentschedulers_db
from neutron.db import allowedaddresspairs_db as addr_pair_db
+from neutron.db import api as db_api
from neutron.db import db_base_plugin_v2
from neutron.db import dvr_mac_db
from neutron.db import external_net_db
def supported_extension_aliases(self):
if not hasattr(self, '_aliases'):
aliases = self._supported_extension_aliases[:]
+ aliases += self.extension_manager.extension_aliases()
sg_rpc.disable_security_group_extension_by_config(aliases)
self._aliases = aliases
return self._aliases
def __init__(self):
# First load drivers, then initialize DB, then initialize drivers
self.type_manager = managers.TypeManager()
+ self.extension_manager = managers.ExtensionManager()
self.mechanism_manager = managers.MechanismManager()
super(Ml2Plugin, self).__init__()
self.type_manager.initialize()
+ self.extension_manager.initialize()
self.mechanism_manager.initialize()
# bulk support depends on the underlying drivers
self.__native_bulk_support = self.mechanism_manager.native_bulk_support
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
attributes.PORTS, ['_ml2_extend_port_dict_binding'])
+ # Register extend dict methods for network and port resources.
+ # Each mechanism driver that supports extend attribute for the resources
+ # can add those attribute to the result.
+ db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
+ attributes.NETWORKS, ['_ml2_md_extend_network_dict'])
+ db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
+ attributes.PORTS, ['_ml2_md_extend_port_dict'])
+ db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
+ attributes.SUBNETS, ['_ml2_md_extend_subnet_dict'])
+
+ def _ml2_md_extend_network_dict(self, result, netdb):
+ session = db_api.get_session()
+ with session.begin(subtransactions=True):
+ self.extension_manager.extend_network_dict(session, result)
+
+ def _ml2_md_extend_port_dict(self, result, portdb):
+ session = db_api.get_session()
+ with session.begin(subtransactions=True):
+ self.extension_manager.extend_port_dict(session, result)
+
+ def _ml2_md_extend_subnet_dict(self, result, subnetdb):
+ session = db_api.get_session()
+ with session.begin(subtransactions=True):
+ self.extension_manager.extend_subnet_dict(session, result)
+
# Note - The following hook methods have "ml2" in their names so
# that they are not called twice during unit tests due to global
# registration of hooks in portbindings_db.py used by other
with session.begin(subtransactions=True):
self._ensure_default_security_group(context, tenant_id)
result = super(Ml2Plugin, self).create_network(context, network)
+ self.extension_manager.process_create_network(session, net_data,
+ result)
self._process_l3_create(context, result, net_data)
net_data['id'] = result['id']
self.type_manager.create_network_segments(context, net_data,
updated_network = super(Ml2Plugin, self).update_network(context,
id,
network)
+ self.extension_manager.process_update_network(session, network,
+ original_network)
self._process_l3_update(context, updated_network,
network['network'])
self.type_manager._extend_network_dict_provider(context,
session = context.session
with session.begin(subtransactions=True):
result = super(Ml2Plugin, self).create_subnet(context, subnet)
+ self.extension_manager.process_create_subnet(session, subnet,
+ result)
mech_context = driver_context.SubnetContext(self, context, result)
self.mechanism_manager.create_subnet_precommit(mech_context)
original_subnet = super(Ml2Plugin, self).get_subnet(context, id)
updated_subnet = super(Ml2Plugin, self).update_subnet(
context, id, subnet)
+ self.extension_manager.process_update_subnet(session, subnet,
+ original_subnet)
mech_context = driver_context.SubnetContext(
self, context, updated_subnet, original_subnet=original_subnet)
self.mechanism_manager.update_subnet_precommit(mech_context)
sgids = self._get_security_groups_on_port(context, port)
dhcp_opts = port['port'].get(edo_ext.EXTRADHCPOPTS, [])
result = super(Ml2Plugin, self).create_port(context, port)
+ self.extension_manager.process_create_port(session, attrs, result)
self._process_port_create_security_group(context, result, sgids)
network = self.get_network(context, result['network_id'])
binding = db.add_port_binding(session, result['id'])
original_port = self._make_port_dict(port_db)
updated_port = super(Ml2Plugin, self).update_port(context, id,
port)
+ self.extension_manager.process_update_port(session, attrs,
+ original_port)
if addr_pair.ADDRESS_PAIRS in port['port']:
need_port_update_notify |= (
self.update_address_pairs_on_port(context, id, port,
--- /dev/null
+# 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.api import extensions
+from neutron.api.v2 import attributes as attr
+
+
+EXTENDED_ATTRIBUTES_2_0 = {
+ 'networks': {
+ 'network_extension': {'allow_post': True,
+ 'allow_put': True,
+ 'default': attr.ATTR_NOT_SPECIFIED,
+ 'is_visible': True,
+ 'enforce_policy': True},
+ },
+ 'subnets': {
+ 'subnet_extension': {'allow_post': True,
+ 'allow_put': True,
+ 'default': attr.ATTR_NOT_SPECIFIED,
+ 'is_visible': True,
+ 'enforce_policy': True},
+ },
+ 'ports': {
+ 'port_extension': {'allow_post': True,
+ 'allow_put': True,
+ 'default': attr.ATTR_NOT_SPECIFIED,
+ 'is_visible': True,
+ 'enforce_policy': True},
+ },
+}
+
+
+class Test_extension(extensions.ExtensionDescriptor):
+
+ @classmethod
+ def get_name(cls):
+ return "ML2 test extension"
+
+ @classmethod
+ def get_alias(cls):
+ return "test_extension"
+
+ @classmethod
+ def get_description(cls):
+ return _("Adds test attributes to core resources.")
+
+ @classmethod
+ def get_namespace(cls):
+ return ("http://docs.openstack.org/ext/neutron/ml2/test/"
+ "test_extension/api/v1.0")
+
+ @classmethod
+ def get_updated(cls):
+ return "2014-07-16T10:00:00-00:00"
+
+ def get_extended_resources(self, version):
+ if version == "2.0":
+ return EXTENDED_ATTRIBUTES_2_0
+ else:
+ return {}
--- /dev/null
+# 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.api import extensions
+from neutron.plugins.ml2 import config
+from neutron.plugins.ml2 import driver_api as api
+from neutron.tests.unit.ml2 import extensions as test_extensions
+from neutron.tests.unit.ml2 import test_ml2_plugin
+
+
+class ExtensionDriverTestCase(test_ml2_plugin.Ml2PluginV2TestCase):
+
+ _extension_drivers = ['test']
+
+ def setUp(self):
+ config.cfg.CONF.set_override('extension_drivers',
+ self._extension_drivers,
+ group='ml2')
+ super(ExtensionDriverTestCase, self).setUp()
+
+ def test_network_attr(self):
+ with self.network() as network:
+ ent = network['network'].get('network_extension')
+ self.assertIsNotNone(ent)
+
+ def test_subnet_attr(self):
+ with self.subnet() as subnet:
+ ent = subnet['subnet'].get('subnet_extension')
+ self.assertIsNotNone(ent)
+
+ def test_port_attr(self):
+ with self.port() as port:
+ ent = port['port'].get('port_extension')
+ self.assertIsNotNone(ent)
+
+
+class TestExtensionDriver(api.ExtensionDriver):
+ _supported_extension_alias = 'test_extension'
+
+ def initialize(self):
+ self.network_extension = 'Test_Network_Extension'
+ self.subnet_extension = 'Test_Subnet_Extension'
+ self.port_extension = 'Test_Port_Extension'
+ extensions.append_api_extensions_path(test_extensions.__path__)
+
+ @property
+ def extension_alias(self):
+ return self._supported_extension_alias
+
+ def process_create_network(self, session, data, result):
+ result['network_extension'] = self.network_extension
+
+ def process_create_subnet(self, session, data, result):
+ result['subnet_extension'] = self.subnet_extension
+
+ def process_create_port(self, session, data, result):
+ result['port_extension'] = self.port_extension
fslsdn = neutron.plugins.ml2.drivers.mechanism_fslsdn:FslsdnMechanismDriver
sriovnicswitch = neutron.plugins.ml2.drivers.mech_sriov.mech_driver:SriovNicSwitchMechanismDriver
nuage = neutron.plugins.ml2.drivers.mech_nuage.driver:NuageMechanismDriver
+neutron.ml2.extension_drivers =
+ test = neutron.tests.unit.ml2.test_extension_driver_api:TestExtensionDriver
neutron.openstack.common.cache.backends =
memory = neutron.openstack.common.cache._backends.memory:MemoryBackend
# These are for backwards compat with Icehouse notification_driver configuration values
neutron.openstack.common.notifier.rpc_notifier = oslo.messaging.notify._impl_messaging:MessagingDriver
neutron.openstack.common.notifier.test_notifier = oslo.messaging.notify._impl_test:TestDriver
-
[build_sphinx]
all_files = 1
build-dir = doc/build