diff --git a/etc/neutron/plugins/ml2/ml2_conf.ini b/etc/neutron/plugins/ml2/ml2_conf.ini index a7fc6478fe..5dc20440b0 100644 --- a/etc/neutron/plugins/ml2/ml2_conf.ini +++ b/etc/neutron/plugins/ml2/ml2_conf.ini @@ -13,6 +13,12 @@ # tenant_network_types = local # 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] # (ListOpt) List of physical_network names with which flat networks # can be created. Use * to allow flat networks with arbitrary diff --git a/neutron/plugins/ml2/README b/neutron/plugins/ml2/README index 38d704dd42..30c0d375a1 100644 --- a/neutron/plugins/ml2/README +++ b/neutron/plugins/ml2/README @@ -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 follow-on effort. -Support for mechanism drivers is currently skeletal. The -MechanismDriver interface is currently a stub, with details to be -defined in future versions. MechanismDrivers will be called both -inside and following DB transactions for network and port -create/update/delete operations. They will also be called to establish -a port binding, determining the VIF type and network segment to be -used. +Support for mechanism drivers is currently a work-in-progress in +pre-release Havana versions, and the interface is subject to change +before the release of Havana. MechanismDrivers are currently called +both inside and following DB transactions for network and port +create/update/delete operations. In a future version, they will also +called to establish a port binding, determining the VIF type and +network segment to be used. The database schema and driver APIs support multi-segment networks, but the client API for multi-segment networks is not yet implemented. diff --git a/neutron/plugins/ml2/common/__init__.py b/neutron/plugins/ml2/common/__init__.py new file mode 100644 index 0000000000..788cea1f70 --- /dev/null +++ b/neutron/plugins/ml2/common/__init__.py @@ -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. diff --git a/neutron/plugins/ml2/common/exceptions.py b/neutron/plugins/ml2/common/exceptions.py new file mode 100644 index 0000000000..ed94b1e1f1 --- /dev/null +++ b/neutron/plugins/ml2/common/exceptions.py @@ -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.") diff --git a/neutron/plugins/ml2/config.py b/neutron/plugins/ml2/config.py index 867fd8fcf9..43e752da76 100644 --- a/neutron/plugins/ml2/config.py +++ b/neutron/plugins/ml2/config.py @@ -29,9 +29,9 @@ ml2_opts = [ "networks.")), cfg.ListOpt('mechanism_drivers', default=[], - help=_("List of networking mechanism driver entrypoints to " - "be loaded from the neutron.ml2.mechanism_drivers " - "namespace.")), + help=_("An ordered list of networking mechanism driver " + "entrypoints to be loaded from the " + "neutron.ml2.mechanism_drivers namespace.")), ] diff --git a/neutron/plugins/ml2/driver_api.py b/neutron/plugins/ml2/driver_api.py index adc969b015..23e7e5d3ef 100644 --- a/neutron/plugins/ml2/driver_api.py +++ b/neutron/plugins/ml2/driver_api.py @@ -13,7 +13,7 @@ # License for the specific language governing permissions and limitations # 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 driver API. These are defined separately from similar keys in @@ -128,15 +128,103 @@ class TypeDriver(object): 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): """Define stable abstract interface for ML2 mechanism drivers. - Note that this is currently a stub class, but it is expected to be - functional for the H-2 milestone. It currently serves mainly to - help solidify the architectural distinction between TypeDrivers - and MechanismDrivers. + A mechanism driver is called on the creation, update, and deletion + of networks and ports. For every event, there are two methods that + get called - one within the database transaction (method suffix of + _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 @abstractmethod @@ -149,10 +237,177 @@ class MechanismDriver(object): """ pass - # TODO(rkukura): Add methods called inside and after transaction - # for create_network, update_network, delete_network, create_port, - # update_port, delete_port, and maybe for port binding - # changes. Exceptions raised by methods called inside transactions - # can rollback, but shouldn't block. Methods called after - # transaction commits can block, and exceptions may cause deletion - # of resource. + def create_network_precommit(self, context): + """Allocate resources for a new network. + + :param context: NetworkContext instance describing the new + network. + + 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 diff --git a/neutron/plugins/ml2/driver_context.py b/neutron/plugins/ml2/driver_context.py new file mode 100644 index 0000000000..a5cf6b743d --- /dev/null +++ b/neutron/plugins/ml2/driver_context.py @@ -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 diff --git a/neutron/plugins/ml2/managers.py b/neutron/plugins/ml2/managers.py index 5832aacd3b..e53bce7f8b 100644 --- a/neutron/plugins/ml2/managers.py +++ b/neutron/plugins/ml2/managers.py @@ -20,6 +20,7 @@ import stevedore from neutron.common import exceptions as exc 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 @@ -108,12 +109,18 @@ class TypeManager(stevedore.named.NamedExtensionManager): class MechanismManager(stevedore.named.NamedExtensionManager): """Manage networking mechanisms using drivers. - Note that this is currently a stub class, but it is expected to be - functional for the H-2 milestone. It currently serves mainly to - help solidify the architectural distinction between TypeDrivers - and MechanismDrivers. + Note that this is still a work in progress, and the interface + may change before the final release of Havana. """ + # 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): # REVISIT(rkukura): Need way to make stevedore use our logging # configuration. Currently, nothing is logged if loading a @@ -125,9 +132,226 @@ class MechanismManager(stevedore.named.NamedExtensionManager): cfg.CONF.ml2.mechanism_drivers, invoke_on_load=True) 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): - 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) diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index c30dbff63f..7d10ae9734 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -30,12 +30,15 @@ from neutron.db import quota_db # noqa from neutron.db import securitygroups_rpc_base as sg_db_rpc from neutron.extensions import portbindings from neutron.extensions import providernet as provider +from neutron.openstack.common import excutils from neutron.openstack.common import importutils from neutron.openstack.common import log 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 db 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 rpc @@ -138,7 +141,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, def _extend_network_dict_provider(self, context, network): id = network['id'] - segments = db.get_network_segments(context.session, id) + segments = self.get_network_segments(context, id) if not segments: LOG.error(_("Network %s has no segments"), id) network[provider.NETWORK_TYPE] = None @@ -170,7 +173,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, session = context.session with session.begin(subtransactions=True): network_id = port['network_id'] - segments = db.get_network_segments(session, network_id) + segments = self.get_network_segments(context, network_id) if not segments: LOG.warning(_("In _notify_port_updated() for port %(port_id), " "network %(network_id) has no segments"), @@ -184,6 +187,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, segment[api.SEGMENTATION_ID], segment[api.PHYSICAL_NETWORK]) + # TODO(apech): Need to override bulk operations + def create_network(self, context, network): attrs = network['network'] segment = self._process_provider_create(context, attrs) @@ -203,7 +208,19 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, # to TypeManager. db.add_network_segment(session, id, segment) 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 def update_network(self, context, id, network): @@ -211,12 +228,24 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, session = context.session with session.begin(subtransactions=True): - result = super(Ml2Plugin, self).update_network(context, id, - network) - self._process_l3_update(context, result, network['network']) - self._extend_network_dict_provider(context, result) + original_network = super(Ml2Plugin, self).get_network(context, id) + updated_network = super(Ml2Plugin, self).update_network(context, + id, + 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): session = context.session @@ -241,16 +270,35 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, 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 with session.begin(subtransactions=True): 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) for segment in segments: self.type_manager.release_segment(session, segment) # The segment records are deleted via cascade from the # 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) def create_port(self, context, port): @@ -266,7 +314,16 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, result) self._process_port_create_security_group(context, result, sgids) 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) return result @@ -285,6 +342,16 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, attrs, 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( context, original_port, updated_port) @@ -326,7 +393,16 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, with session.begin(subtransactions=True): self.disassociate_floatingips(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) 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) diff --git a/neutron/tests/unit/ml2/drivers/__init__.py b/neutron/tests/unit/ml2/drivers/__init__.py new file mode 100644 index 0000000000..788cea1f70 --- /dev/null +++ b/neutron/tests/unit/ml2/drivers/__init__.py @@ -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. diff --git a/neutron/tests/unit/ml2/drivers/mechanism_logger.py b/neutron/tests/unit/ml2/drivers/mechanism_logger.py new file mode 100644 index 0000000000..03b4316e6b --- /dev/null +++ b/neutron/tests/unit/ml2/drivers/mechanism_logger.py @@ -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) diff --git a/neutron/tests/unit/ml2/drivers/mechanism_test.py b/neutron/tests/unit/ml2/drivers/mechanism_test.py new file mode 100644 index 0000000000..bd49672267 --- /dev/null +++ b/neutron/tests/unit/ml2/drivers/mechanism_test.py @@ -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) diff --git a/neutron/tests/unit/ml2/test_ml2_plugin.py b/neutron/tests/unit/ml2/test_ml2_plugin.py index 17987b43e2..c5ff52f80d 100644 --- a/neutron/tests/unit/ml2/test_ml2_plugin.py +++ b/neutron/tests/unit/ml2/test_ml2_plugin.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # 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_db_plugin as test_plugin @@ -25,6 +26,13 @@ class Ml2PluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase): _plugin_name = PLUGIN_NAME 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) self.port_create_status = 'DOWN' diff --git a/setup.cfg b/setup.cfg index 22acb1afe8..70d01d630e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -107,6 +107,9 @@ neutron.ml2.type_drivers = flat = neutron.plugins.ml2.drivers.type_flat:FlatTypeDriver local = neutron.plugins.ml2.drivers.type_local:LocalTypeDriver 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] all_files = 1