From 317c8f318f82aed4174839ba1fe256d65cff59bc Mon Sep 17 00:00:00 2001 From: Zang MingJie Date: Mon, 26 Aug 2013 17:31:46 +0800 Subject: [PATCH] subnet calls of ml2 mechanism driver Implements blueprint ml2-mechanism-driver-subnet-calls Change-Id: Ib9a8009975959692f46f80fb2a850fcc2b22debe --- neutron/plugins/ml2/driver_api.py | 124 +++++++++++++++++- neutron/plugins/ml2/driver_context.py | 22 ++++ neutron/plugins/ml2/managers.py | 86 ++++++++++++ neutron/plugins/ml2/plugin.py | 48 +++++++ .../unit/ml2/drivers/mechanism_logger.py | 39 +++++- .../tests/unit/ml2/drivers/mechanism_test.py | 50 +++++-- 6 files changed, 351 insertions(+), 18 deletions(-) diff --git a/neutron/plugins/ml2/driver_api.py b/neutron/plugins/ml2/driver_api.py index 5bc7ca839..546c23c94 100644 --- a/neutron/plugins/ml2/driver_api.py +++ b/neutron/plugins/ml2/driver_api.py @@ -164,6 +164,38 @@ class NetworkContext(object): pass +class SubnetContext(object): + """Context passed to MechanismDrivers for changes to subnet resources. + + A SubnetContext instance wraps a subnet 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 subnet. + + Return the current state of the subnet, as defined by + NeutronPluginBaseV2.create_subnet and all extensions in the + ml2 plugin. + """ + pass + + @abstractproperty + def original(self): + """Return the original state of the subnet. + + Return the original state of the subnet, prior to a call to + update_subnet. Method is only valid within calls to + update_subnet_precommit and update_subnet_postcommit. + """ + pass + + class PortContext(object): """Context passed to MechanismDrivers for changes to port resources. @@ -222,8 +254,6 @@ class MechanismDriver(object): methods that are part of the database transaction. """ - # TODO(apech): add calls for subnets - __metaclass__ = ABCMeta @abstractmethod @@ -326,6 +356,96 @@ class MechanismDriver(object): """ pass + def create_subnet_precommit(self, context): + """Allocate resources for a new subnet. + + :param context: SubnetContext instance describing the new + subnet. + + Create a new subnet, 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_subnet_postcommit(self, context): + """Create a subnet. + + :param context: SubnetContext instance describing the new + subnet. + + 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_subnet_precommit(self, context): + """Update resources of a subnet. + + :param context: SubnetContext instance describing the new + state of the subnet, as well as the original state prior + to the update_subnet call. + + Update values of a subnet, updating the associated resources + in the database. Called inside transaction context on session. + Raising an exception will result in rollback of the + transaction. + + update_subnet_precommit is called for all changes to the + subnet 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_subnet_postcommit(self, context): + """Update a subnet. + + :param context: SubnetContext instance describing the new + state of the subnet, as well as the original state prior + to the update_subnet 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_subnet_postcommit is called for all changes to the + subnet 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_subnet_precommit(self, context): + """Delete resources for a subnet. + + :param context: SubnetContext instance describing the current + state of the subnet, prior to the call to delete it. + + Delete subnet resources previously allocated by this + mechanism driver for a subnet. 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_subnet_postcommit(self, context): + """Delete a subnet. + + :param context: SubnetContext instance describing the current + state of the subnet, 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. diff --git a/neutron/plugins/ml2/driver_context.py b/neutron/plugins/ml2/driver_context.py index a5cf6b743..1f552d2d8 100644 --- a/neutron/plugins/ml2/driver_context.py +++ b/neutron/plugins/ml2/driver_context.py @@ -35,12 +35,15 @@ class NetworkContext(MechanismDriverContext, api.NetworkContext): self._original_network = original_network self._segments = segments + @property def current(self): return self._network + @property def original(self): return self._original_network + @property def network_segments(self): if not self._segments: self._segments = self._plugin.get_network_segments( @@ -48,6 +51,22 @@ class NetworkContext(MechanismDriverContext, api.NetworkContext): return self._segments +class SubnetContext(MechanismDriverContext, api.SubnetContext): + + def __init__(self, plugin, plugin_context, subnet, original_subnet=None): + super(SubnetContext, self).__init__(plugin, plugin_context) + self._subnet = subnet + self._original_subnet = original_subnet + + @property + def current(self): + return self._subnet + + @property + def original(self): + return self._original_subnet + + class PortContext(MechanismDriverContext, api.PortContext): def __init__(self, plugin, plugin_context, port, @@ -57,12 +76,15 @@ class PortContext(MechanismDriverContext, api.PortContext): self._original_port = original_port self._network_context = None + @property def current(self): return self._port + @property def original(self): return self._original_port + @property def network(self): """Return the NetworkContext associated with this port.""" if not self._network_context: diff --git a/neutron/plugins/ml2/managers.py b/neutron/plugins/ml2/managers.py index 3e30e534c..36c4ab988 100644 --- a/neutron/plugins/ml2/managers.py +++ b/neutron/plugins/ml2/managers.py @@ -270,6 +270,92 @@ class MechanismManager(stevedore.named.NamedExtensionManager): self._call_on_drivers("delete_network_postcommit", context, continue_on_failure=True) + def create_subnet_precommit(self, context): + """Notify all mechanism drivers of a subnet creation. + + :raises: neutron.plugins.ml2.common.MechanismDriverError + if any mechanism driver create_subnet_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_subnet_precommit", context) + + def create_subnet_postcommit(self, context): + """Notify all mechanism drivers of subnet creation. + + :raises: neutron.plugins.ml2.common.MechanismDriverError + if any mechanism driver create_subnet_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 subnet 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_subnet_postcommit", context) + + def update_subnet_precommit(self, context): + """Notify all mechanism drivers of a subnet update. + + :raises: neutron.plugins.ml2.common.MechanismDriverError + if any mechanism driver update_subnet_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_subnet_precommit", context) + + def update_subnet_postcommit(self, context): + """Notify all mechanism drivers of a subnet update. + + :raises: neutron.plugins.ml2.common.MechanismDriverError + if any mechanism driver update_subnet_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 subnet. There is no + guarantee that all mechanism drivers are called in this case. + """ + self._call_on_drivers("update_subnet_postcommit", context) + + def delete_subnet_precommit(self, context): + """Notify all mechanism drivers of a subnet deletion. + + :raises: neutron.plugins.ml2.common.MechanismDriverError + if any mechanism driver delete_subnet_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_subnet_precommit", context) + + def delete_subnet_postcommit(self, context): + """Notify all mechanism drivers of a subnet deletion. + + :raises: neutron.plugins.ml2.common.MechanismDriverError + if any mechanism driver delete_subnet_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 + subnet resource has already been deleted from the database + and it doesn't make sense to undo the action by recreating the + subnet. + """ + self._call_on_drivers("delete_subnet_postcommit", context, + continue_on_failure=True) + def create_port_precommit(self, context): """Notify all mechanism drivers of a port creation. diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index bb42aab76..c14243ddd 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -311,6 +311,54 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, pass self.notifier.network_delete(context, id) + def create_subnet(self, context, subnet): + session = context.session + with session.begin(subtransactions=True): + result = super(Ml2Plugin, self).create_subnet(context, subnet) + mech_context = driver_context.SubnetContext(self, context, result) + self.mechanism_manager.create_subnet_precommit(mech_context) + + try: + self.mechanism_manager.create_subnet_postcommit(mech_context) + except ml2_exc.MechanismDriverError: + with excutils.save_and_reraise_exception(): + LOG.error(_("mechanism_manager.create_subnet failed, " + "deleting subnet '%s'"), result['id']) + self.delete_subnet(context, result['id']) + return result + + def update_subnet(self, context, id, subnet): + session = context.session + with session.begin(subtransactions=True): + original_subnet = super(Ml2Plugin, self).get_subnet(context, id) + updated_subnet = super(Ml2Plugin, self).update_subnet( + context, id, subnet) + mech_context = driver_context.SubnetContext( + self, context, updated_subnet, original_subnet=original_subnet) + self.mechanism_manager.update_subnet_precommit(mech_context) + + # TODO(apech) - handle errors raised by update_subnet, potentially + # by re-calling update_subnet 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_subnet_postcommit(mech_context) + return updated_subnet + + def delete_subnet(self, context, id): + session = context.session + with session.begin(subtransactions=True): + subnet = self.get_subnet(context, id) + mech_context = driver_context.SubnetContext(self, context, subnet) + self.mechanism_manager.delete_subnet_precommit(mech_context) + super(Ml2Plugin, self).delete_subnet(context, id) + try: + self.mechanism_manager.delete_subnet_postcommit(mech_context) + except ml2_exc.MechanismDriverError: + # TODO(apech) - One or more mechanism driver failed to + # delete the subnet. Ideally we'd notify the caller of + # the fact that an error occurred. + pass + def create_port(self, context, port): attrs = port['port'] attrs['status'] = const.PORT_STATUS_DOWN diff --git a/neutron/tests/unit/ml2/drivers/mechanism_logger.py b/neutron/tests/unit/ml2/drivers/mechanism_logger.py index 03b4316e6..c900fdfe1 100644 --- a/neutron/tests/unit/ml2/drivers/mechanism_logger.py +++ b/neutron/tests/unit/ml2/drivers/mechanism_logger.py @@ -33,9 +33,9 @@ class LoggerMechanismDriver(api.MechanismDriver): "(original settings %(original)s) and " "network segments %(segments)s"), {'method': method_name, - 'current': context.current(), - 'original': context.original(), - 'segments': context.network_segments()}) + 'current': context.current, + 'original': context.original, + 'segments': context.network_segments}) def create_network_precommit(self, context): self._log_network_call("create_network_precommit", context) @@ -55,14 +55,39 @@ class LoggerMechanismDriver(api.MechanismDriver): def delete_network_postcommit(self, context): self._log_network_call("delete_network_postcommit", context) + def _log_subnet_call(self, method_name, context): + LOG.info(_("%(method)s called with subnet settings %(current)s " + "(original settings %(original)s)"), + {'method': method_name, + 'current': context.current, + 'original': context.original}) + + def create_subnet_precommit(self, context): + self._log_subnet_call("create_subnet_precommit", context) + + def create_subnet_postcommit(self, context): + self._log_subnet_call("create_subnet_postcommit", context) + + def update_subnet_precommit(self, context): + self._log_subnet_call("update_subnet_precommit", context) + + def update_subnet_postcommit(self, context): + self._log_subnet_call("update_subnet_postcommit", context) + + def delete_subnet_precommit(self, context): + self._log_subnet_call("delete_subnet_precommit", context) + + def delete_subnet_postcommit(self, context): + self._log_subnet_call("delete_subnet_postcommit", context) + def _log_port_call(self, method_name, context): - network_context = context.network() + 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()}) + 'current': context.current, + 'original': context.original, + 'network': network_context.current}) def create_port_precommit(self, context): self._log_port_call("create_port_precommit", context) diff --git a/neutron/tests/unit/ml2/drivers/mechanism_test.py b/neutron/tests/unit/ml2/drivers/mechanism_test.py index bd4967226..bc1ce6f6d 100644 --- a/neutron/tests/unit/ml2/drivers/mechanism_test.py +++ b/neutron/tests/unit/ml2/drivers/mechanism_test.py @@ -25,12 +25,14 @@ class TestMechanismDriver(api.MechanismDriver): def _check_network_context(self, context, original_expected): assert(isinstance(context, driver_context.NetworkContext)) - assert(context.current()) + assert(isinstance(context.current, dict)) + assert(context.current['id'] is not None) if original_expected: - assert(context.original()) + assert(isinstance(context.original, dict)) + assert(context.current['id'] == context.original['id']) else: - assert(not context.original()) - assert(context.network_segments()) + assert(not context.original) + assert(context.network_segments) def create_network_precommit(self, context): self._check_network_context(context, False) @@ -50,15 +52,45 @@ class TestMechanismDriver(api.MechanismDriver): def delete_network_postcommit(self, context): self._check_network_context(context, False) + def _check_subnet_context(self, context, original_expected): + assert(isinstance(context, driver_context.SubnetContext)) + assert(isinstance(context.current, dict)) + assert(context.current['id'] is not None) + if original_expected: + assert(isinstance(context.original, dict)) + assert(context.current['id'] == context.original['id']) + else: + assert(not context.original) + + def create_subnet_precommit(self, context): + self._check_subnet_context(context, False) + + def create_subnet_postcommit(self, context): + self._check_subnet_context(context, False) + + def update_subnet_precommit(self, context): + self._check_subnet_context(context, True) + + def update_subnet_postcommit(self, context): + self._check_subnet_context(context, True) + + def delete_subnet_precommit(self, context): + self._check_subnet_context(context, False) + + def delete_subnet_postcommit(self, context): + self._check_subnet_context(context, False) + def _check_port_context(self, context, original_expected): assert(isinstance(context, driver_context.PortContext)) - assert(context.current()) + assert(isinstance(context.current, dict)) + assert(context.current['id'] is not None) if original_expected: - assert(context.original()) + assert(isinstance(context.original, dict)) + assert(context.current['id'] == context.original['id']) else: - assert(not context.original()) - network_context = context.network() - assert(network_context) + assert(not context.original) + network_context = context.network + assert(isinstance(network_context, driver_context.NetworkContext)) self._check_network_context(network_context, False) def create_port_precommit(self, context): -- 2.45.2