Browse Source

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
tags/11.0.0.0b1
Kevin Benton 2 years ago
parent
commit
fc563eaabe

+ 6
- 2
neutron/plugins/ml2/driver_context.py View File

@@ -13,6 +13,8 @@
13 13
 #    License for the specific language governing permissions and limitations
14 14
 #    under the License.
15 15
 
16
+import copy
17
+
16 18
 from neutron_lib.api.definitions import portbindings
17 19
 from neutron_lib import constants
18 20
 from oslo_log import log
@@ -95,8 +97,10 @@ class PortContext(MechanismDriverContext, api.PortContext):
95 97
         self._original_port = original_port
96 98
         self._network_context = NetworkContext(plugin, plugin_context,
97 99
                                                network) if network else None
98
-        self._binding = binding
99
-        self._binding_levels = binding_levels
100
+        # NOTE(kevinbenton): these copys can go away once we are working with
101
+        # OVO objects here instead of native SQLA objects.
102
+        self._binding = copy.deepcopy(binding)
103
+        self._binding_levels = copy.deepcopy(binding_levels)
100 104
         self._segments_to_bind = None
101 105
         self._new_bound_segment = None
102 106
         self._next_segments_to_bind = None

+ 11
- 2
neutron/plugins/ml2/plugin.py View File

@@ -13,6 +13,8 @@
13 13
 #    License for the specific language governing permissions and limitations
14 14
 #    under the License.
15 15
 
16
+import copy
17
+
16 18
 from eventlet import greenthread
17 19
 from neutron_lib.api.definitions import portbindings
18 20
 from neutron_lib.api.definitions import provider_net
@@ -339,6 +341,10 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
339 341
             binding.host = ''
340 342
 
341 343
         self._update_port_dict_binding(port, binding)
344
+        # merging here brings binding changes into the session so they can be
345
+        # committed since the binding attached to the context is detached from
346
+        # the session
347
+        plugin_context.session.merge(binding)
342 348
         return changes
343 349
 
344 350
     @db_api.retry_db_errors
@@ -507,6 +513,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
507 513
                                         cur_binding.host)
508 514
                 db.set_binding_levels(plugin_context,
509 515
                                       bind_context._binding_levels)
516
+                # refresh context with a snapshot of updated state
517
+                cur_context._binding = copy.deepcopy(cur_binding)
510 518
                 cur_context._binding_levels = bind_context._binding_levels
511 519
 
512 520
                 # Update PortContext's port dictionary to reflect the
@@ -1337,6 +1345,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
1337 1345
         self._update_port_dict_binding(port, binding)
1338 1346
         binding.host = attrs and attrs.get(portbindings.HOST_ID)
1339 1347
         binding.router_id = attrs and attrs.get('device_id')
1348
+        # merge into session to reflect changes
1349
+        plugin_context.session.merge(binding)
1340 1350
 
1341 1351
     @utils.transaction_guard
1342 1352
     @db_api.retry_if_session_inactive()
@@ -1557,8 +1567,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
1557 1567
                     context, port['id'], host)
1558 1568
                 if not binding:
1559 1569
                     return
1560
-                binding['status'] = status
1561
-                binding.update(binding)
1570
+                binding.status = status
1562 1571
                 updated = True
1563 1572
 
1564 1573
         if (updated and

+ 5
- 2
neutron/tests/unit/plugins/ml2/test_plugin.py View File

@@ -1606,7 +1606,8 @@ class TestMl2PortBinding(Ml2PluginV2TestCase,
1606 1606
             plugin = directory.get_plugin()
1607 1607
             binding = plugin._get_port(
1608 1608
                 self.context, port['port']['id']).port_binding
1609
-            binding['host'] = 'test'
1609
+            with self.context.session.begin(subtransactions=True):
1610
+                binding.host = 'test'
1610 1611
             mech_context = driver_context.PortContext(
1611 1612
                 plugin, self.context, port['port'],
1612 1613
                 plugin.get_network(self.context, port['port']['network_id']),
@@ -1614,7 +1615,9 @@ class TestMl2PortBinding(Ml2PluginV2TestCase,
1614 1615
         with mock.patch('neutron.plugins.ml2.plugin.Ml2Plugin.'
1615 1616
                         '_update_port_dict_binding') as update_mock:
1616 1617
             attrs = {portbindings.HOST_ID: None}
1617
-            plugin._process_port_binding(mech_context, attrs)
1618
+            self.assertEqual('test', binding.host)
1619
+            with self.context.session.begin(subtransactions=True):
1620
+                plugin._process_port_binding(mech_context, attrs)
1618 1621
             self.assertTrue(update_mock.mock_calls)
1619 1622
             self.assertEqual('', binding.host)
1620 1623
 

+ 2
- 1
neutron/tests/unit/plugins/ml2/test_port_binding.py View File

@@ -18,6 +18,7 @@ from neutron_lib.api.definitions import portbindings
18 18
 from neutron_lib import constants as const
19 19
 from neutron_lib import context
20 20
 from neutron_lib.plugins import directory
21
+from oslo_serialization import jsonutils
21 22
 
22 23
 from neutron.conf.plugins.ml2.drivers import driver_type
23 24
 from neutron.plugins.ml2 import config
@@ -201,7 +202,7 @@ class PortBindingTestCase(test_plugin.NeutronDbPluginV2TestCase):
201 202
                 port_id=original_port['id'],
202 203
                 host=original_port['binding:host_id'],
203 204
                 vnic_type=original_port['binding:vnic_type'],
204
-                profile=original_port['binding:profile'],
205
+                profile=jsonutils.dumps(original_port['binding:profile']),
205 206
                 vif_type=original_port['binding:vif_type'],
206 207
                 vif_details=original_port['binding:vif_details'])
207 208
             levels = 1

Loading…
Cancel
Save