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. Closes-Bug: #1669528 Change-Id: Ib5ba2daa80acba53c082bade1f61a3ee44ca41fc
This commit is contained in:
parent
aeee7a5c90
commit
fc563eaabe
|
@ -13,6 +13,8 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
|
||||
from neutron_lib.api.definitions import portbindings
|
||||
from neutron_lib import constants
|
||||
from oslo_log import log
|
||||
|
@ -95,8 +97,10 @@ class PortContext(MechanismDriverContext, api.PortContext):
|
|||
self._original_port = original_port
|
||||
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
|
||||
|
|
|
@ -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.definitions import portbindings
|
||||
from neutron_lib.api.definitions import provider_net
|
||||
|
@ -339,6 +341,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
|
||||
plugin_context.session.merge(binding)
|
||||
return changes
|
||||
|
||||
@db_api.retry_db_errors
|
||||
|
@ -507,6 +513,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
|||
cur_binding.host)
|
||||
db.set_binding_levels(plugin_context,
|
||||
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
|
||||
|
@ -1337,6 +1345,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
|
||||
plugin_context.session.merge(binding)
|
||||
|
||||
@utils.transaction_guard
|
||||
@db_api.retry_if_session_inactive()
|
||||
|
@ -1557,8 +1567,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
|||
context, port['id'], host)
|
||||
if not binding:
|
||||
return
|
||||
binding['status'] = status
|
||||
binding.update(binding)
|
||||
binding.status = status
|
||||
updated = True
|
||||
|
||||
if (updated and
|
||||
|
|
|
@ -1606,7 +1606,8 @@ class TestMl2PortBinding(Ml2PluginV2TestCase,
|
|||
plugin = directory.get_plugin()
|
||||
binding = plugin._get_port(
|
||||
self.context, port['port']['id']).port_binding
|
||||
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']),
|
||||
|
@ -1614,7 +1615,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)
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ from neutron_lib.api.definitions import portbindings
|
|||
from neutron_lib import constants as const
|
||||
from neutron_lib import context
|
||||
from neutron_lib.plugins import directory
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from neutron.conf.plugins.ml2.drivers import driver_type
|
||||
from neutron.plugins.ml2 import config
|
||||
|
@ -201,7 +202,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
|
||||
|
|
Loading…
Reference in New Issue