From cb773098d276cfbabc600361e91cb709ec846ba8 Mon Sep 17 00:00:00 2001 From: Kevin Benton Date: Fri, 3 Mar 2017 08:51:33 -0800 Subject: [PATCH] deepcopy binding and binding levels avoid expiration Perform a deepcopy on the sqla objects passed into the PortContext so we get detached versions of them safe to reference forever. This is necessary because the PortContexts outlive the transaction context managers they are creating in which means an object can be expired and result in a query after a commit (e.g. in bind_port_if_needed) that will fail and result in an exception. This required a few additional explicit session.merge calls to deal with cases where touching the mech context was implicitly expected to modify the DB state on the next commit. Conflicts: neutron/plugins/ml2/driver_context.py neutron/plugins/ml2/plugin.py neutron/tests/unit/plugins/ml2/test_port_binding.py Closes-Bug: #1669528 Change-Id: Ib5ba2daa80acba53c082bade1f61a3ee44ca41fc (cherry picked from commit 20c1de9dc83875431e8bf995f3f4f85bd6d81955) --- neutron/plugins/ml2/driver_context.py | 8 ++++++-- neutron/plugins/ml2/plugin.py | 13 +++++++++++-- neutron/tests/unit/plugins/ml2/test_plugin.py | 9 ++++++--- neutron/tests/unit/plugins/ml2/test_port_binding.py | 3 ++- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/neutron/plugins/ml2/driver_context.py b/neutron/plugins/ml2/driver_context.py index d18bb25d6c2..bf7b503d6ce 100644 --- a/neutron/plugins/ml2/driver_context.py +++ b/neutron/plugins/ml2/driver_context.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +import copy + from neutron_lib import constants from oslo_log import log from oslo_serialization import jsonutils @@ -96,8 +98,10 @@ class PortContext(MechanismDriverContext, api.PortContext): else: self._network_context = NetworkContext( plugin, plugin_context, network) if network else None - self._binding = binding - self._binding_levels = binding_levels + # NOTE(kevinbenton): these copys can go away once we are working with + # OVO objects here instead of native SQLA objects. + self._binding = copy.deepcopy(binding) + self._binding_levels = copy.deepcopy(binding_levels) self._segments_to_bind = None self._new_bound_segment = None self._next_segments_to_bind = None diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index dd3554a22c4..3ccb2476df0 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +import copy + from eventlet import greenthread from neutron_lib.api import validators from neutron_lib import constants as const @@ -348,6 +350,10 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, binding.host = '' self._update_port_dict_binding(port, binding) + # merging here brings binding changes into the session so they can be + # committed since the binding attached to the context is detached from + # the session + mech_context._plugin_context.session.merge(binding) return changes def _bind_port_if_needed(self, context, allow_notify=False, @@ -510,6 +516,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, cur_binding.vif_details = new_binding.vif_details db.clear_binding_levels(session, port_id, cur_binding.host) db.set_binding_levels(session, bind_context._binding_levels) + # refresh context with a snapshot of updated state + cur_context._binding = copy.deepcopy(cur_binding) cur_context._binding_levels = bind_context._binding_levels # Update PortContext's port dictionary to reflect the @@ -1414,6 +1422,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, self._update_port_dict_binding(port, binding) binding.host = attrs and attrs.get(portbindings.HOST_ID) binding.router_id = attrs and attrs.get('device_id') + # merge into session to reflect changes + mech_context._plugin_context.session.merge(binding) @utils.transaction_guard @db_api.retry_if_session_inactive() @@ -1635,8 +1645,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, session, port['id'], host) if not binding: return - binding['status'] = status - binding.update(binding) + binding.status = status updated = True if (updated and diff --git a/neutron/tests/unit/plugins/ml2/test_plugin.py b/neutron/tests/unit/plugins/ml2/test_plugin.py index 488a6617242..69d48341875 100644 --- a/neutron/tests/unit/plugins/ml2/test_plugin.py +++ b/neutron/tests/unit/plugins/ml2/test_plugin.py @@ -1596,7 +1596,8 @@ class TestMl2PortBinding(Ml2PluginV2TestCase, plugin = manager.NeutronManager.get_plugin() binding = ml2_db.get_locked_port_and_binding(self.context.session, port['port']['id'])[1] - binding['host'] = 'test' + with self.context.session.begin(subtransactions=True): + binding.host = 'test' mech_context = driver_context.PortContext( plugin, self.context, port['port'], plugin.get_network(self.context, port['port']['network_id']), @@ -1604,7 +1605,9 @@ class TestMl2PortBinding(Ml2PluginV2TestCase, with mock.patch('neutron.plugins.ml2.plugin.Ml2Plugin.' '_update_port_dict_binding') as update_mock: attrs = {portbindings.HOST_ID: None} - plugin._process_port_binding(mech_context, attrs) + self.assertEqual('test', binding.host) + with self.context.session.begin(subtransactions=True): + plugin._process_port_binding(mech_context, attrs) self.assertTrue(update_mock.mock_calls) self.assertEqual('', binding.host) @@ -2507,7 +2510,7 @@ class TestMl2PluginCreateUpdateDeletePort(base.BaseTestCase): binding.host = 'vm_host' binding.vnic_type = portbindings.VNIC_NORMAL binding.profile = '' - binding.vif_type = '' + binding.vif_type = 'unbound' binding.vif_details = '' with mock.patch.object(ml2_plugin.Ml2Plugin, '__init__') as init,\ diff --git a/neutron/tests/unit/plugins/ml2/test_port_binding.py b/neutron/tests/unit/plugins/ml2/test_port_binding.py index f4fd1419dcd..bab7ffd9064 100644 --- a/neutron/tests/unit/plugins/ml2/test_port_binding.py +++ b/neutron/tests/unit/plugins/ml2/test_port_binding.py @@ -15,6 +15,7 @@ import mock from neutron_lib import constants as const +from oslo_serialization import jsonutils from neutron import context from neutron.extensions import portbindings @@ -194,7 +195,7 @@ class PortBindingTestCase(test_plugin.NeutronDbPluginV2TestCase): port_id=original_port['id'], host=original_port['binding:host_id'], vnic_type=original_port['binding:vnic_type'], - profile=original_port['binding:profile'], + profile=jsonutils.dumps(original_port['binding:profile']), vif_type=original_port['binding:vif_type'], vif_details=original_port['binding:vif_details']) levels = 1