Initial Modular L2 Mechanism Driver implementation.

Define the Mechanism Driver interface for create/update/delete
operations on networks and ports. For each of these event, the
Mechanism Driver provides one method that is called within the
database transaction of the ml2 plugin method, and one that is called
after the transaction is completed.

Support for mechanism drivers is still a work-in-progress, and the
interface is subject to change in future versions before the release
of Havana. However this initial version should be sufficient to enable
others to start defining their own mechanism drivers.

Change-Id: Ife30215589792ee27df9897d3b2bc04392638266
Implements: blueprint ml2-mechanism-drivers
Fixes: bug #1199977
Fixes: bug #1199978
DocImpact
This commit is contained in:
Andre Pech 2013-07-07 13:00:54 -07:00
parent 370d97fedd
commit ab44545a76
14 changed files with 897 additions and 37 deletions

View File

@ -13,6 +13,12 @@
# tenant_network_types = local # tenant_network_types = local
# Example: tenant_network_types = vlan,gre # Example: tenant_network_types = vlan,gre
# (ListOpt) Ordered list of networking mechanism driver entrypoints
# to be loaded from the neutron.ml2.mechanism_drivers namespace.
# mechanism_drivers =
# Example: mechanism_drivers = arista
# Example: mechanism_drivers = cisco,logger
[ml2_type_flat] [ml2_type_flat]
# (ListOpt) List of physical_network names with which flat networks # (ListOpt) List of physical_network names with which flat networks
# can be created. Use * to allow flat networks with arbitrary # can be created. Use * to allow flat networks with arbitrary

View File

@ -31,13 +31,13 @@ openvswitch and linuxbridge plugins' L2 agents, and should also work
with the hyperv L2 agent. A modular agent may be developed as a with the hyperv L2 agent. A modular agent may be developed as a
follow-on effort. follow-on effort.
Support for mechanism drivers is currently skeletal. The Support for mechanism drivers is currently a work-in-progress in
MechanismDriver interface is currently a stub, with details to be pre-release Havana versions, and the interface is subject to change
defined in future versions. MechanismDrivers will be called both before the release of Havana. MechanismDrivers are currently called
inside and following DB transactions for network and port both inside and following DB transactions for network and port
create/update/delete operations. They will also be called to establish create/update/delete operations. In a future version, they will also
a port binding, determining the VIF type and network segment to be called to establish a port binding, determining the VIF type and
used. network segment to be used.
The database schema and driver APIs support multi-segment networks, The database schema and driver APIs support multi-segment networks,
but the client API for multi-segment networks is not yet implemented. but the client API for multi-segment networks is not yet implemented.

View File

@ -0,0 +1,14 @@
# Copyright (c) 2013 OpenStack Foundation
# 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.

View File

@ -0,0 +1,23 @@
# Copyright (c) 2013 OpenStack Foundation
# 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.
"""Exceptions used by ML2."""
from neutron.common import exceptions
class MechanismDriverError(exceptions.NeutronException):
"""Mechanism driver call failed."""
message = _("%(method)s failed.")

View File

@ -29,9 +29,9 @@ ml2_opts = [
"networks.")), "networks.")),
cfg.ListOpt('mechanism_drivers', cfg.ListOpt('mechanism_drivers',
default=[], default=[],
help=_("List of networking mechanism driver entrypoints to " help=_("An ordered list of networking mechanism driver "
"be loaded from the neutron.ml2.mechanism_drivers " "entrypoints to be loaded from the "
"namespace.")), "neutron.ml2.mechanism_drivers namespace.")),
] ]

View File

@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod, abstractproperty
# The following keys are used in the segment dictionaries passed via # The following keys are used in the segment dictionaries passed via
# the driver API. These are defined separately from similar keys in # the driver API. These are defined separately from similar keys in
@ -128,15 +128,103 @@ class TypeDriver(object):
pass pass
class NetworkContext(object):
"""Context passed to MechanismDrivers for changes to network resources.
A NetworkContext instance wraps a network resource. It provides
helper methods for accessing other relevant information. Results
from expensive operations are cached so that other
MechanismDrivers can freely access the same information.
"""
__metaclass__ = ABCMeta
@abstractproperty
def current(self):
"""Return the current state of the network.
Return the current state of the network, as defined by
NeutronPluginBaseV2.create_network and all extensions in the
ml2 plugin.
"""
pass
@abstractproperty
def original(self):
"""Return the original state of the network.
Return the original state of the network, prior to a call to
update_network. Method is only valid within calls to
update_network_precommit and update_network_postcommit.
"""
pass
@abstractproperty
def network_segments(self):
"""Return the segments associated with this network resource."""
pass
class PortContext(object):
"""Context passed to MechanismDrivers for changes to port resources.
A PortContext instance wraps a port resource. It provides helper
methods for accessing other relevant information. Results from
expensive operations are cached so that other MechanismDrivers can
freely access the same information.
"""
__metaclass__ = ABCMeta
@abstractproperty
def current(self):
"""Return the current state of the port.
Return the current state of the port, as defined by
NeutronPluginBaseV2.create_port and all extensions in the ml2
plugin.
"""
pass
@abstractproperty
def original(self):
"""Return the original state of the port
Return the original state of the port, prior to a call to
update_port. Method is only valid within calls to
update_port_precommit and update_port_postcommit.
"""
pass
@abstractproperty
def network(self):
"""Return the NetworkContext associated with this port."""
pass
class MechanismDriver(object): class MechanismDriver(object):
"""Define stable abstract interface for ML2 mechanism drivers. """Define stable abstract interface for ML2 mechanism drivers.
Note that this is currently a stub class, but it is expected to be A mechanism driver is called on the creation, update, and deletion
functional for the H-2 milestone. It currently serves mainly to of networks and ports. For every event, there are two methods that
help solidify the architectural distinction between TypeDrivers get called - one within the database transaction (method suffix of
and MechanismDrivers. _precommit), one right afterwards (method suffix of _postcommit).
Exceptions raised by methods called inside the transaction can
rollback, but should not make any blocking calls (for example,
REST requests to an outside controller). Methods called after
transaction commits can make blocking external calls, though these
will block the entire process. Exceptions raised in calls after
the transaction commits may cause the associated resource to be
deleted.
Because rollback outside of the transaction is not done in the
update network/port case, all data validation must be done within
methods that are part of the database transaction.
""" """
# TODO(apech): add calls for subnets
__metaclass__ = ABCMeta __metaclass__ = ABCMeta
@abstractmethod @abstractmethod
@ -149,10 +237,177 @@ class MechanismDriver(object):
""" """
pass pass
# TODO(rkukura): Add methods called inside and after transaction def create_network_precommit(self, context):
# for create_network, update_network, delete_network, create_port, """Allocate resources for a new network.
# update_port, delete_port, and maybe for port binding
# changes. Exceptions raised by methods called inside transactions :param context: NetworkContext instance describing the new
# can rollback, but shouldn't block. Methods called after network.
# transaction commits can block, and exceptions may cause deletion
# of resource. Create a new network, allocating resources as necessary in the
database. Called inside transaction context on session. Call
cannot block. Raising an exception will result in a rollback
of the current transaction.
"""
pass
def create_network_postcommit(self, context):
"""Create a network.
:param context: NetworkContext instance describing the new
network.
Called after the transaction commits. Call can block, though
will block the entire process so care should be taken to not
drastically affect performance. Raising an exception will
cause the deletion of the resource.
"""
pass
def update_network_precommit(self, context):
"""Update resources of a network.
:param context: NetworkContext instance describing the new
state of the network, as well as the original state prior
to the update_network call.
Update values of a network, updating the associated resources
in the database. Called inside transaction context on session.
Raising an exception will result in rollback of the
transaction.
update_network_precommit is called for all changes to the
network state. It is up to the mechanism driver to ignore
state or state changes that it does not know or care about.
"""
pass
def update_network_postcommit(self, context):
"""Update a network.
:param context: NetworkContext instance describing the new
state of the network, as well as the original state prior
to the update_network call.
Called after the transaction commits. Call can block, though
will block the entire process so care should be taken to not
drastically affect performance. Raising an exception will
cause the deletion of the resource.
update_network_postcommit is called for all changes to the
network state. It is up to the mechanism driver to ignore
state or state changes that it does not know or care about.
"""
pass
def delete_network_precommit(self, context):
"""Delete resources for a network.
:param context: NetworkContext instance describing the current
state of the network, prior to the call to delete it.
Delete network resources previously allocated by this
mechanism driver for a network. Called inside transaction
context on session. Runtime errors are not expected, but
raising an exception will result in rollback of the
transaction.
"""
pass
def delete_network_postcommit(self, context):
"""Delete a network.
:param context: NetworkContext instance describing the current
state of the network, prior to the call to delete it.
Called after the transaction commits. Call can block, though
will block the entire process so care should be taken to not
drastically affect performance. Runtime errors are not
expected, and will not prevent the resource from being
deleted.
"""
pass
def create_port_precommit(self, context):
"""Allocate resources for a new port.
:param context: PortContext instance describing the port.
Create a new port, allocating resources as necessary in the
database. Called inside transaction context on session. Call
cannot block. Raising an exception will result in a rollback
of the current transaction.
"""
pass
def create_port_postcommit(self, context):
"""Create a port.
:param context: PortContext instance describing the port.
Called after the transaction completes. Call can block, though
will block the entire process so care should be taken to not
drastically affect performance. Raising an exception will
result in the deletion of the resource.
"""
pass
def update_port_precommit(self, context):
"""Update resources of a port.
:param context: PortContext instance describing the new
state of the port, as well as the original state prior
to the update_port call.
Called inside transaction context on session to complete a
port update as defined by this mechanism driver. Raising an
exception will result in rollback of the transaction.
update_port_precommit is called for all changes to the port
state. It is up to the mechanism driver to ignore state or
state changes that it does not know or care about.
"""
pass
def update_port_postcommit(self, context):
"""Update a port.
:param context: PortContext instance describing the new
state of the port, as well as the original state prior
to the update_port call.
Called after the transaction completes. Call can block, though
will block the entire process so care should be taken to not
drastically affect performance. Raising an exception will
result in the deletion of the resource.
update_port_postcommit is called for all changes to the port
state. It is up to the mechanism driver to ignore state or
state changes that it does not know or care about.
"""
pass
def delete_port_precommit(self, context):
"""Delete resources of a port.
:param context: PortContext instance describing the current
state of the port, prior to the call to delete it.
Called inside transaction context on session. Runtime errors
are not expected, but raising an exception will result in
rollback of the transaction.
"""
pass
def delete_port_postcommit(self, context):
"""Delete a port.
:param context: PortContext instance describing the current
state of the port, prior to the call to delete it.
Called after the transaction completes. Call can block, though
will block the entire process so care should be taken to not
drastically affect performance. Runtime errors are not
expected, and will not prevent the resource from being
deleted.
"""
pass

View File

@ -0,0 +1,74 @@
# Copyright (c) 2013 OpenStack Foundation
# 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.plugins.ml2 import driver_api as api
class MechanismDriverContext(object):
"""MechanismDriver context base class."""
def __init__(self, plugin, plugin_context):
self._plugin = plugin
# This temporarily creates a reference loop, but the
# lifetime of PortContext is limited to a single
# method call of the plugin.
self._plugin_context = plugin_context
class NetworkContext(MechanismDriverContext, api.NetworkContext):
def __init__(self, plugin, plugin_context, network,
segments=None, original_network=None):
super(NetworkContext, self).__init__(plugin, plugin_context)
self._network = network
self._original_network = original_network
self._segments = segments
def current(self):
return self._network
def original(self):
return self._original_network
def network_segments(self):
if not self._segments:
self._segments = self._plugin.get_network_segments(
self._plugin_context, self._network['id'])
return self._segments
class PortContext(MechanismDriverContext, api.PortContext):
def __init__(self, plugin, plugin_context, port,
original_port=None):
super(PortContext, self).__init__(plugin, plugin_context)
self._port = port
self._original_port = original_port
self._network_context = None
def current(self):
return self._port
def original(self):
return self._original_port
def network(self):
"""Return the NetworkContext associated with this port."""
if not self._network_context:
network = self._plugin.get_network(self._plugin_context,
self._port["network_id"])
self._network_context = NetworkContext(self._plugin,
self._plugin_context,
network)
return self._network_context

View File

@ -20,6 +20,7 @@ import stevedore
from neutron.common import exceptions as exc from neutron.common import exceptions as exc
from neutron.openstack.common import log from neutron.openstack.common import log
from neutron.plugins.ml2.common import exceptions as ml2_exc
from neutron.plugins.ml2 import driver_api as api from neutron.plugins.ml2 import driver_api as api
@ -108,12 +109,18 @@ class TypeManager(stevedore.named.NamedExtensionManager):
class MechanismManager(stevedore.named.NamedExtensionManager): class MechanismManager(stevedore.named.NamedExtensionManager):
"""Manage networking mechanisms using drivers. """Manage networking mechanisms using drivers.
Note that this is currently a stub class, but it is expected to be Note that this is still a work in progress, and the interface
functional for the H-2 milestone. It currently serves mainly to may change before the final release of Havana.
help solidify the architectural distinction between TypeDrivers
and MechanismDrivers.
""" """
# TODO(apech): add calls for subnets
# Registered mechanism drivers, keyed by name.
mech_drivers = {}
# Ordered list of mechanism drivers, defining
# the order in which the drivers are called.
ordered_mech_drivers = []
def __init__(self): def __init__(self):
# REVISIT(rkukura): Need way to make stevedore use our logging # REVISIT(rkukura): Need way to make stevedore use our logging
# configuration. Currently, nothing is logged if loading a # configuration. Currently, nothing is logged if loading a
@ -125,9 +132,226 @@ class MechanismManager(stevedore.named.NamedExtensionManager):
cfg.CONF.ml2.mechanism_drivers, cfg.CONF.ml2.mechanism_drivers,
invoke_on_load=True) invoke_on_load=True)
LOG.info(_("Loaded mechanism driver names: %s"), self.names()) LOG.info(_("Loaded mechanism driver names: %s"), self.names())
# TODO(rkukura): Register mechanisms. self._register_mechanisms()
def _register_mechanisms(self):
"""Register all mechanism drivers.
This method should only be called once in the MechanismManager
constructor.
"""
for ext in self:
if ext.name in self.mech_drivers:
LOG.error(_("Mechanism driver '%s' ignored because "
"driver is already registered"),
ext.name)
else:
self.mech_drivers[ext.name] = ext
self.ordered_mech_drivers.append(ext)
LOG.info(_("Registered mechanism drivers: %s"),
[driver.name for driver in self.ordered_mech_drivers])
def initialize(self): def initialize(self):
pass for driver in self.ordered_mech_drivers:
LOG.info(_("Initializing mechanism driver '%s'"), driver.name)
driver.obj.initialize()
# TODO(rkukura): Define mechanism dispatch methods def _call_on_drivers(self, method_name, context,
continue_on_failure=False):
"""Helper method for calling a method across all mechanism drivers.
:param method_name: name of the method to call
:param context: context parameter to pass to each method call
:param continue_on_failure: whether or not to continue to call
all mechanism drivers once one has raised an exception
:raises: neutron.plugins.ml2.common.MechanismDriverError
if any mechanism driver call fails.
"""
error = False
for driver in self.ordered_mech_drivers:
try:
getattr(driver.obj, method_name)(context)
except Exception:
LOG.exception(
_("Mechanism driver '%(name)s' failed in %(method)s"),
{'name': driver.name, 'method': method_name}
)
error = True
if not continue_on_failure:
break
if error:
raise ml2_exc.MechanismDriverError(
method=method_name
)
def create_network_precommit(self, context):
"""Notify all mechanism drivers of a network creation.
:raises: neutron.plugins.ml2.common.MechanismDriverError
if any mechanism driver create_network_precommit call fails.
Called within the database transaction. If a mechanism driver
raises an exception, then a MechanismDriverError is propogated
to the caller, triggering a rollback. There is no guarantee
that all mechanism drivers are called in this case.
"""
self._call_on_drivers("create_network_precommit", context)
def create_network_postcommit(self, context):
"""Notify all mechanism drivers of network creation.
:raises: neutron.plugins.ml2.common.MechanismDriverError
if any mechanism driver create_network_postcommit call fails.
Called after the database transaction. If a mechanism driver
raises an exception, then a MechanismDriverError is propagated
to the caller, where the network will be deleted, triggering
any required cleanup. There is no guarantee that all mechanism
drivers are called in this case.
"""
self._call_on_drivers("create_network_postcommit", context)
def update_network_precommit(self, context):
"""Notify all mechanism drivers of a network update.
:raises: neutron.plugins.ml2.common.MechanismDriverError
if any mechanism driver update_network_precommit call fails.
Called within the database transaction. If a mechanism driver
raises an exception, then a MechanismDriverError is propogated
to the caller, triggering a rollback. There is no guarantee
that all mechanism drivers are called in this case.
"""
self._call_on_drivers("update_network_precommit", context)
def update_network_postcommit(self, context):
"""Notify all mechanism drivers of a network update.
:raises: neutron.plugins.ml2.common.MechanismDriverError
if any mechanism driver update_network_postcommit call fails.
Called after the database transaction. If a mechanism driver
raises an exception, then a MechanismDriverError is propagated
to the caller, where an error is returned to the user. The
user is expected to take the appropriate action, whether by
retrying the call or deleting the network. There is no
guarantee that all mechanism drivers are called in this case.
"""
self._call_on_drivers("update_network_postcommit", context)
def delete_network_precommit(self, context):
"""Notify all mechanism drivers of a network deletion.
:raises: neutron.plugins.ml2.common.MechanismDriverError
if any mechanism driver delete_network_precommit call fails.
Called within the database transaction. If a mechanism driver
raises an exception, then a MechanismDriverError is propogated
to the caller, triggering a rollback. There is no guarantee
that all mechanism drivers are called in this case.
"""
self._call_on_drivers("delete_network_precommit", context)
def delete_network_postcommit(self, context):
"""Notify all mechanism drivers of a network deletion.
:raises: neutron.plugins.ml2.common.MechanismDriverError
if any mechanism driver delete_network_postcommit call fails.
Called after the database transaction. If any mechanism driver
raises an error, then the error is logged but we continue to
call every other mechanism driver. A MechanismDriverError is
then reraised at the end to notify the caller of a failure. In
general we expect the caller to ignore the error, as the
network resource has already been deleted from the database
and it doesn't make sense to undo the action by recreating the
network.
"""
self._call_on_drivers("delete_network_postcommit", context,
continue_on_failure=True)
def create_port_precommit(self, context):
"""Notify all mechanism drivers of a port creation.
:raises: neutron.plugins.ml2.common.MechanismDriverError
if any mechanism driver create_port_precommit call fails.
Called within the database transaction. If a mechanism driver
raises an exception, then a MechanismDriverError is propogated
to the caller, triggering a rollback. There is no guarantee
that all mechanism drivers are called in this case.
"""
self._call_on_drivers("create_port_precommit", context)
def create_port_postcommit(self, context):
"""Notify all mechanism drivers of port creation.
:raises: neutron.plugins.ml2.common.MechanismDriverError
if any mechanism driver create_port_postcommit call fails.
Called after the database transaction. Errors raised by
mechanism drivers are left to propogate to the caller, where
the port will be deleted, triggering any required
cleanup. There is no guarantee that all mechanism drivers are
called in this case.
"""
self._call_on_drivers("create_port_postcommit", context)
def update_port_precommit(self, context):
"""Notify all mechanism drivers of a port update.
:raises: neutron.plugins.ml2.common.MechanismDriverError
if any mechanism driver update_port_precommit call fails.
Called within the database transaction. If a mechanism driver
raises an exception, then a MechanismDriverError is propogated
to the caller, triggering a rollback. There is no guarantee
that all mechanism drivers are called in this case.
"""
self._call_on_drivers("update_port_precommit", context)
def update_port_postcommit(self, context):
"""Notify all mechanism drivers of a port update.
:raises: neutron.plugins.ml2.common.MechanismDriverError
if any mechanism driver update_port_postcommit call fails.
Called after the database transaction. If a mechanism driver
raises an exception, then a MechanismDriverError is propagated
to the caller, where an error is returned to the user. The
user is expected to take the appropriate action, whether by
retrying the call or deleting the port. There is no
guarantee that all mechanism drivers are called in this case.
"""
self._call_on_drivers("update_port_postcommit", context)
def delete_port_precommit(self, context):
"""Notify all mechanism drivers of a port deletion.
:raises: neutron.plugins.ml2.common.MechanismDriverError
if any mechanism driver delete_port_precommit call fails.
Called within the database transaction. If a mechanism driver
raises an exception, then a MechanismDriverError is propogated
to the caller, triggering a rollback. There is no guarantee
that all mechanism drivers are called in this case.
"""
self._call_on_drivers("delete_port_precommit", context)
def delete_port_postcommit(self, context):
"""Notify all mechanism drivers of a port deletion.
:raises: neutron.plugins.ml2.common.MechanismDriverError
if any mechanism driver delete_port_postcommit call fails.
Called after the database transaction. If any mechanism driver
raises an error, then the error is logged but we continue to
call every other mechanism driver. A MechanismDriverError is
then reraised at the end to notify the caller of a failure. In
general we expect the caller to ignore the error, as the
port resource has already been deleted from the database
and it doesn't make sense to undo the action by recreating the
port.
"""
self._call_on_drivers("delete_port_postcommit", context,
continue_on_failure=True)

View File

@ -30,12 +30,15 @@ from neutron.db import quota_db # noqa
from neutron.db import securitygroups_rpc_base as sg_db_rpc from neutron.db import securitygroups_rpc_base as sg_db_rpc
from neutron.extensions import portbindings from neutron.extensions import portbindings
from neutron.extensions import providernet as provider from neutron.extensions import providernet as provider
from neutron.openstack.common import excutils
from neutron.openstack.common import importutils from neutron.openstack.common import importutils
from neutron.openstack.common import log from neutron.openstack.common import log
from neutron.openstack.common import rpc as c_rpc from neutron.openstack.common import rpc as c_rpc
from neutron.plugins.ml2.common import exceptions as ml2_exc
from neutron.plugins.ml2 import config # noqa from neutron.plugins.ml2 import config # noqa
from neutron.plugins.ml2 import db from neutron.plugins.ml2 import db
from neutron.plugins.ml2 import driver_api as api from neutron.plugins.ml2 import driver_api as api
from neutron.plugins.ml2 import driver_context
from neutron.plugins.ml2 import managers from neutron.plugins.ml2 import managers
from neutron.plugins.ml2 import rpc from neutron.plugins.ml2 import rpc
@ -138,7 +141,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
def _extend_network_dict_provider(self, context, network): def _extend_network_dict_provider(self, context, network):
id = network['id'] id = network['id']
segments = db.get_network_segments(context.session, id) segments = self.get_network_segments(context, id)
if not segments: if not segments:
LOG.error(_("Network %s has no segments"), id) LOG.error(_("Network %s has no segments"), id)
network[provider.NETWORK_TYPE] = None network[provider.NETWORK_TYPE] = None
@ -170,7 +173,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
session = context.session session = context.session
with session.begin(subtransactions=True): with session.begin(subtransactions=True):
network_id = port['network_id'] network_id = port['network_id']
segments = db.get_network_segments(session, network_id) segments = self.get_network_segments(context, network_id)
if not segments: if not segments:
LOG.warning(_("In _notify_port_updated() for port %(port_id), " LOG.warning(_("In _notify_port_updated() for port %(port_id), "
"network %(network_id) has no segments"), "network %(network_id) has no segments"),
@ -184,6 +187,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
segment[api.SEGMENTATION_ID], segment[api.SEGMENTATION_ID],
segment[api.PHYSICAL_NETWORK]) segment[api.PHYSICAL_NETWORK])
# TODO(apech): Need to override bulk operations
def create_network(self, context, network): def create_network(self, context, network):
attrs = network['network'] attrs = network['network']
segment = self._process_provider_create(context, attrs) segment = self._process_provider_create(context, attrs)
@ -203,7 +208,19 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
# to TypeManager. # to TypeManager.
db.add_network_segment(session, id, segment) db.add_network_segment(session, id, segment)
self._extend_network_dict_provider(context, result) self._extend_network_dict_provider(context, result)
mech_context = driver_context.NetworkContext(self,
context,
result,
segments=[segment])
self.mechanism_manager.create_network_precommit(mech_context)
try:
self.mechanism_manager.create_network_postcommit(mech_context)
except ml2_exc.MechanismDriverError:
with excutils.save_and_reraise_exception():
LOG.error(_("mechanism_manager.create_network failed, "
"deleting network '%s'"), result['id'])
self.delete_network(context, result['id'])
return result return result
def update_network(self, context, id, network): def update_network(self, context, id, network):
@ -211,12 +228,24 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
session = context.session session = context.session
with session.begin(subtransactions=True): with session.begin(subtransactions=True):
result = super(Ml2Plugin, self).update_network(context, id, original_network = super(Ml2Plugin, self).get_network(context, id)
network) updated_network = super(Ml2Plugin, self).update_network(context,
self._process_l3_update(context, result, network['network']) id,
self._extend_network_dict_provider(context, result) network)
self._process_l3_update(context, updated_network,
network['network'])
self._extend_network_dict_provider(context, updated_network)
mech_context = driver_context.NetworkContext(
self, context, updated_network,
original_network=original_network)
self.mechanism_manager.update_network_precommit(mech_context)
return result # TODO(apech) - handle errors raised by update_network, potentially
# by re-calling update_network with the previous attributes. For
# now the error is propogated to the caller, which is expected to
# either undo/retry the operation or delete the resource.
self.mechanism_manager.update_network_postcommit(mech_context)
return updated_network
def get_network(self, context, id, fields=None): def get_network(self, context, id, fields=None):
session = context.session session = context.session
@ -241,16 +270,35 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
return [self._fields(net, fields) for net in nets] return [self._fields(net, fields) for net in nets]
def delete_network(self, context, id): def get_network_segments(self, context, id):
session = context.session session = context.session
with session.begin(subtransactions=True): with session.begin(subtransactions=True):
segments = db.get_network_segments(session, id) segments = db.get_network_segments(session, id)
return segments
def delete_network(self, context, id):
session = context.session
with session.begin(subtransactions=True):
network = self.get_network(context, id)
segments = self.get_network_segments(context, id)
mech_context = driver_context.NetworkContext(self,
context,
network,
segments=segments)
self.mechanism_manager.delete_network_precommit(mech_context)
super(Ml2Plugin, self).delete_network(context, id) super(Ml2Plugin, self).delete_network(context, id)
for segment in segments: for segment in segments:
self.type_manager.release_segment(session, segment) self.type_manager.release_segment(session, segment)
# The segment records are deleted via cascade from the # The segment records are deleted via cascade from the
# network record, so explicit removal is not necessary. # network record, so explicit removal is not necessary.
try:
self.mechanism_manager.delete_network_postcommit(mech_context)
except ml2_exc.MechanismDriverError:
# TODO(apech) - One or more mechanism driver failed to
# delete the network. Ideally we'd notify the caller of
# the fact that an error occurred.
pass
self.notifier.network_delete(context, id) self.notifier.network_delete(context, id)
def create_port(self, context, port): def create_port(self, context, port):
@ -266,7 +314,16 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
result) result)
self._process_port_create_security_group(context, result, sgids) self._process_port_create_security_group(context, result, sgids)
self._extend_port_dict_binding(context, result) self._extend_port_dict_binding(context, result)
mech_context = driver_context.PortContext(self, context, result)
self.mechanism_manager.create_port_precommit(mech_context)
try:
self.mechanism_manager.create_port_postcommit(mech_context)
except ml2_exc.MechanismDriverError:
with excutils.save_and_reraise_exception():
LOG.error(_("mechanism_manager.create_port failed, "
"deleting port '%s'"), result['id'])
self.delete_port(context, result['id'])
self.notify_security_groups_member_updated(context, result) self.notify_security_groups_member_updated(context, result)
return result return result
@ -285,6 +342,16 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
attrs, attrs,
updated_port) updated_port)
self._extend_port_dict_binding(context, updated_port) self._extend_port_dict_binding(context, updated_port)
mech_context = driver_context.PortContext(
self, context, updated_port,
original_port=original_port)
self.mechanism_manager.update_port_precommit(mech_context)
# TODO(apech) - handle errors raised by update_port, potentially
# by re-calling update_port with the previous attributes. For
# now the error is propogated to the caller, which is expected to
# either undo/retry the operation or delete the resource.
self.mechanism_manager.update_port_postcommit(mech_context)
need_port_update_notify |= self.is_security_group_member_updated( need_port_update_notify |= self.is_security_group_member_updated(
context, original_port, updated_port) context, original_port, updated_port)
@ -326,7 +393,16 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
with session.begin(subtransactions=True): with session.begin(subtransactions=True):
self.disassociate_floatingips(context, id) self.disassociate_floatingips(context, id)
port = self.get_port(context, id) port = self.get_port(context, id)
mech_context = driver_context.PortContext(self, context, port)
self.mechanism_manager.delete_port_precommit(mech_context)
self._delete_port_security_group_bindings(context, id) self._delete_port_security_group_bindings(context, id)
super(Ml2Plugin, self).delete_port(context, id) super(Ml2Plugin, self).delete_port(context, id)
try:
self.mechanism_manager.delete_port_postcommit(mech_context)
except ml2_exc.MechanismDriverError:
# TODO(apech) - One or more mechanism driver failed to
# delete the port. Ideally we'd notify the caller of the
# fact that an error occurred.
pass
self.notify_security_groups_member_updated(context, port) self.notify_security_groups_member_updated(context, port)

View File

@ -0,0 +1,14 @@
# Copyright (c) 2013 OpenStack Foundation
# 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.

View File

@ -0,0 +1,83 @@
# Copyright (c) 2013 OpenStack Foundation
# 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.openstack.common import log
from neutron.plugins.ml2 import driver_api as api
LOG = log.getLogger(__name__)
class LoggerMechanismDriver(api.MechanismDriver):
"""Mechanism driver that logs all calls and parameters made.
Generally used for testing and debugging.
"""
def initialize(self):
pass
def _log_network_call(self, method_name, context):
LOG.info(_("%(method)s called with network settings %(current)s "
"(original settings %(original)s) and "
"network segments %(segments)s"),
{'method': method_name,
'current': context.current(),
'original': context.original(),
'segments': context.network_segments()})
def create_network_precommit(self, context):
self._log_network_call("create_network_precommit", context)
def create_network_postcommit(self, context):
self._log_network_call("create_network_postcommit", context)
def update_network_precommit(self, context):
self._log_network_call("update_network_precommit", context)
def update_network_postcommit(self, context):
self._log_network_call("update_network_postcommit", context)
def delete_network_precommit(self, context):
self._log_network_call("delete_network_precommit", context)
def delete_network_postcommit(self, context):
self._log_network_call("delete_network_postcommit", context)
def _log_port_call(self, method_name, context):
network_context = context.network()
LOG.info(_("%(method)s called with port settings %(current)s "
"(original settings %(original)s) on network %(network)s"),
{'method': method_name,
'current': context.current(),
'original': context.original(),
'network': network_context.current()})
def create_port_precommit(self, context):
self._log_port_call("create_port_precommit", context)
def create_port_postcommit(self, context):
self._log_port_call("create_port_postcommit", context)
def update_port_precommit(self, context):
self._log_port_call("update_port_precommit", context)
def update_port_postcommit(self, context):
self._log_port_call("update_port_postcommit", context)
def delete_port_precommit(self, context):
self._log_port_call("delete_port_precommit", context)
def delete_port_postcommit(self, context):
self._log_port_call("delete_port_postcommit", context)

View File

@ -0,0 +1,80 @@
# Copyright (c) 2013 OpenStack Foundation
# 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.plugins.ml2 import driver_api as api
from neutron.plugins.ml2 import driver_context
class TestMechanismDriver(api.MechanismDriver):
"""Test mechanism driver for testing mechanism driver api."""
def initialize(self):
pass
def _check_network_context(self, context, original_expected):
assert(isinstance(context, driver_context.NetworkContext))
assert(context.current())
if original_expected:
assert(context.original())
else:
assert(not context.original())
assert(context.network_segments())
def create_network_precommit(self, context):
self._check_network_context(context, False)
def create_network_postcommit(self, context):
self._check_network_context(context, False)
def update_network_precommit(self, context):
self._check_network_context(context, True)
def update_network_postcommit(self, context):
self._check_network_context(context, True)
def delete_network_precommit(self, context):
self._check_network_context(context, False)
def delete_network_postcommit(self, context):
self._check_network_context(context, False)
def _check_port_context(self, context, original_expected):
assert(isinstance(context, driver_context.PortContext))
assert(context.current())
if original_expected:
assert(context.original())
else:
assert(not context.original())
network_context = context.network()
assert(network_context)
self._check_network_context(network_context, False)
def create_port_precommit(self, context):
self._check_port_context(context, False)
def create_port_postcommit(self, context):
self._check_port_context(context, False)
def update_port_precommit(self, context):
self._check_port_context(context, True)
def update_port_postcommit(self, context):
self._check_port_context(context, True)
def delete_port_precommit(self, context):
self._check_port_context(context, False)
def delete_port_postcommit(self, context):
self._check_port_context(context, False)

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from neutron.plugins.ml2 import config as config
from neutron.tests.unit import _test_extension_portbindings as test_bindings from neutron.tests.unit import _test_extension_portbindings as test_bindings
from neutron.tests.unit import test_db_plugin as test_plugin from neutron.tests.unit import test_db_plugin as test_plugin
@ -25,6 +26,13 @@ class Ml2PluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
_plugin_name = PLUGIN_NAME _plugin_name = PLUGIN_NAME
def setUp(self): def setUp(self):
# Enable the test mechanism driver to ensure that
# we can successfully call through to all mechanism
# driver apis.
config.cfg.CONF.set_override('mechanism_drivers',
['logger', 'test'],
'ml2')
self.addCleanup(config.cfg.CONF.reset)
super(Ml2PluginV2TestCase, self).setUp(PLUGIN_NAME) super(Ml2PluginV2TestCase, self).setUp(PLUGIN_NAME)
self.port_create_status = 'DOWN' self.port_create_status = 'DOWN'

View File

@ -107,6 +107,9 @@ neutron.ml2.type_drivers =
flat = neutron.plugins.ml2.drivers.type_flat:FlatTypeDriver flat = neutron.plugins.ml2.drivers.type_flat:FlatTypeDriver
local = neutron.plugins.ml2.drivers.type_local:LocalTypeDriver local = neutron.plugins.ml2.drivers.type_local:LocalTypeDriver
vlan = neutron.plugins.ml2.drivers.type_vlan:VlanTypeDriver vlan = neutron.plugins.ml2.drivers.type_vlan:VlanTypeDriver
neutron.ml2.mechanism_drivers =
logger = neutron.tests.unit.ml2.drivers.mechanism_logger:LoggerMechanismDriver
test = neutron.tests.unit.ml2.drivers.mechanism_test:TestMechanismDriver
[build_sphinx] [build_sphinx]
all_files = 1 all_files = 1