Support for extensions in ML2

The current ML2 plugin supports only extensions defined in the plugin and it
does not support any extensions in the mechanism drivers. The changes in this
commit allows mechanism drivers to define new extensions.

Change-Id: I28da19fabf6de2e9f0d687f875aaaa24c8bbc4f0
Implements: blueprint extensions-in-ml2
This commit is contained in:
Nader Lahouti 2014-04-19 16:01:05 -07:00
parent 2ae77d0329
commit feb3660344
11 changed files with 449 additions and 2 deletions

View File

@ -21,6 +21,11 @@
# 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

View File

@ -679,6 +679,6 @@ def get_extensions_path():
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]))

View File

@ -807,6 +807,8 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
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,

View File

@ -30,6 +30,11 @@ ml2_opts = [
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.")),
]

View File

@ -649,3 +649,158 @@ class MechanismDriver(object):
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

View File

@ -573,3 +573,107 @@ class MechanismManager(stevedore.named.NamedExtensionManager):
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})

View File

@ -35,6 +35,7 @@ from neutron.common import utils
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
@ -110,6 +111,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
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
@ -117,9 +119,11 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
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
@ -411,6 +415,31 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
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
@ -460,6 +489,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
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,
@ -487,6 +518,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
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,
@ -627,6 +660,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
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)
@ -645,6 +680,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
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)
@ -737,6 +774,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
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'])
@ -791,6 +829,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
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,

View File

@ -0,0 +1,69 @@
# 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 {}

View File

@ -0,0 +1,66 @@
# 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

View File

@ -176,6 +176,8 @@ neutron.ml2.mechanism_drivers =
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
@ -186,7 +188,6 @@ oslo.messaging.notify.drivers =
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