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:
Kevin Benton 2017-03-03 08:51:33 -08:00
parent aeee7a5c90
commit fc563eaabe
4 changed files with 24 additions and 7 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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