Browse Source

Merge "Populate binding levels when concurrent ops fail" into stable/rocky

changes/22/676922/1
Zuul 1 month ago
parent
commit
2cd3480fb4
2 changed files with 76 additions and 0 deletions
  1. 16
    0
      neutron/plugins/ml2/plugin.py
  2. 60
    0
      neutron/tests/unit/plugins/ml2/test_plugin.py

+ 16
- 0
neutron/plugins/ml2/plugin.py View File

@@ -666,6 +666,22 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
666 666
                 # Call the mechanism driver precommit methods, commit
667 667
                 # the results, and call the postcommit methods.
668 668
                 self.mechanism_manager.update_port_precommit(cur_context)
669
+            else:
670
+                # Try to populate the PortContext with the current binding
671
+                # levels so that the RPC notification won't get suppressed.
672
+                # This is to avoid leaving ports stuck in a DOWN state.
673
+                # For more information see bug:
674
+                # https://bugs.launchpad.net/neutron/+bug/1755810
675
+                LOG.warning("Concurrent port binding operations failed on "
676
+                            "port %s", port_id)
677
+                levels = db.get_binding_levels(plugin_context, port_id,
678
+                                               cur_binding.host)
679
+                for level in levels:
680
+                    cur_context._push_binding_level(level)
681
+                # refresh context with a snapshot of the current binding state
682
+                cur_context._binding = driver_context.InstanceSnapshot(
683
+                    cur_binding)
684
+
669 685
         if commit:
670 686
             # Continue, using the port state as of the transaction that
671 687
             # just finished, whether that transaction committed new

+ 60
- 0
neutron/tests/unit/plugins/ml2/test_plugin.py View File

@@ -2090,6 +2090,66 @@ class TestMl2PortBinding(Ml2PluginV2TestCase,
2090 2090
                     # Successful binding should only be attempted once.
2091 2091
                     self.assertEqual(1, at_mock.call_count)
2092 2092
 
2093
+    def test__bind_port_if_needed_concurrent_calls(self):
2094
+        port_vif_type = portbindings.VIF_TYPE_UNBOUND
2095
+        bound_vif_type = portbindings.VIF_TYPE_OVS
2096
+
2097
+        plugin, port_context, bound_context = (
2098
+            self._create_port_and_bound_context(port_vif_type,
2099
+                                                bound_vif_type))
2100
+        bound_context._binding_levels = [mock.Mock(
2101
+            port_id="port_id",
2102
+            level=0,
2103
+            driver='fake_agent',
2104
+            segment_id="11111111-2222-3333-4444-555555555555")]
2105
+
2106
+        # let _commit_port_binding replace the PortContext with a new instance
2107
+        # which does not have any binding levels set to simulate the concurrent
2108
+        # port binding operations fail
2109
+        with mock.patch(
2110
+            'neutron.plugins.ml2.plugin.Ml2Plugin._bind_port',
2111
+            return_value=bound_context),\
2112
+            mock.patch('neutron.plugins.ml2.plugin.Ml2Plugin.'
2113
+                       '_notify_port_updated') as npu_mock,\
2114
+            mock.patch('neutron.plugins.ml2.plugin.Ml2Plugin.'
2115
+                       '_attempt_binding',
2116
+                       side_effect=plugin._attempt_binding) as ab_mock,\
2117
+            mock.patch('neutron.plugins.ml2.plugin.Ml2Plugin.'
2118
+                       '_commit_port_binding', return_value=(
2119
+                            mock.MagicMock(), True, True)) as cpb_mock:
2120
+            ret_context = plugin._bind_port_if_needed(port_context,
2121
+                                                      allow_notify=True)
2122
+            # _attempt_binding will return without doing anything during
2123
+            # the second iteration since _should_bind_port returns False
2124
+            self.assertEqual(2, ab_mock.call_count)
2125
+            self.assertEqual(1, cpb_mock.call_count)
2126
+            # _notify_port_updated will still be called though it does
2127
+            # nothing due to the missing binding levels
2128
+            npu_mock.assert_called_once_with(ret_context)
2129
+
2130
+    def test__commit_port_binding_populating_with_binding_levels(self):
2131
+        port_vif_type = portbindings.VIF_TYPE_OVS
2132
+        bound_vif_type = portbindings.VIF_TYPE_OVS
2133
+
2134
+        plugin, port_context, bound_context = (
2135
+            self._create_port_and_bound_context(port_vif_type,
2136
+                                                bound_vif_type))
2137
+        db_portbinding = port_obj.PortBindingLevel(
2138
+            self.context,
2139
+            port_id=uuidutils.generate_uuid(),
2140
+            level=0,
2141
+            driver='fake_agent',
2142
+            segment_id="11111111-2222-3333-4444-555555555555")
2143
+        bound_context.network.current = {'id': 'net_id'}
2144
+
2145
+        with mock.patch.object(ml2_db, 'get_binding_levels',
2146
+                return_value=[db_portbinding]),\
2147
+                mock.patch.object(driver_context.PortContext,
2148
+                '_push_binding_level') as pbl_mock:
2149
+            plugin._commit_port_binding(
2150
+                    port_context, bound_context, True, False)
2151
+            pbl_mock.assert_called_once_with(db_portbinding)
2152
+
2093 2153
     def test_port_binding_profile_not_changed(self):
2094 2154
         profile = {'e': 5}
2095 2155
         profile_arg = {portbindings.PROFILE: profile}

Loading…
Cancel
Save