Browse Source

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

changes/12/663712/3
Zuul 1 month ago
parent
commit
f2d2c4a0ab
2 changed files with 77 additions and 0 deletions
  1. 16
    0
      neutron/plugins/ml2/plugin.py
  2. 61
    0
      neutron/tests/unit/plugins/ml2/test_plugin.py

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

@@ -629,6 +629,22 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
629 629
                 # Call the mechanism driver precommit methods, commit
630 630
                 # the results, and call the postcommit methods.
631 631
                 self.mechanism_manager.update_port_precommit(cur_context)
632
+            else:
633
+                # Try to populate the PortContext with the current binding
634
+                # levels so that the RPC notification won't get suppressed.
635
+                # This is to avoid leaving ports stuck in a DOWN state.
636
+                # For more information see bug:
637
+                # https://bugs.launchpad.net/neutron/+bug/1755810
638
+                LOG.warning("Concurrent port binding operations failed on "
639
+                            "port %s", port_id)
640
+                levels = db.get_binding_levels(plugin_context, port_id,
641
+                                               cur_binding.host)
642
+                for level in levels:
643
+                    cur_context._push_binding_level(level)
644
+                # refresh context with a snapshot of the current binding state
645
+                cur_context._binding = driver_context.InstanceSnapshot(
646
+                    cur_binding)
647
+
632 648
         if commit:
633 649
             # Continue, using the port state as of the transaction that
634 650
             # just finished, whether that transaction committed new

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

@@ -48,6 +48,7 @@ from neutron.db import provisioning_blocks
48 48
 from neutron.db import segments_db
49 49
 from neutron.extensions import multiprovidernet as mpnet
50 50
 from neutron.objects import base as base_obj
51
+from neutron.objects import ports as port_obj
51 52
 from neutron.objects import router as l3_obj
52 53
 from neutron.plugins.ml2.common import exceptions as ml2_exc
53 54
 from neutron.plugins.ml2 import db as ml2_db
@@ -1970,6 +1971,66 @@ class TestMl2PortBinding(Ml2PluginV2TestCase,
1970 1971
                     # Successful binding should only be attempted once.
1971 1972
                     self.assertEqual(1, at_mock.call_count)
1972 1973
 
1974
+    def test__bind_port_if_needed_concurrent_calls(self):
1975
+        port_vif_type = portbindings.VIF_TYPE_UNBOUND
1976
+        bound_vif_type = portbindings.VIF_TYPE_OVS
1977
+
1978
+        plugin, port_context, bound_context = (
1979
+            self._create_port_and_bound_context(port_vif_type,
1980
+                                                bound_vif_type))
1981
+        bound_context._binding_levels = [mock.Mock(
1982
+            port_id="port_id",
1983
+            level=0,
1984
+            driver='fake_agent',
1985
+            segment_id="11111111-2222-3333-4444-555555555555")]
1986
+
1987
+        # let _commit_port_binding replace the PortContext with a new instance
1988
+        # which does not have any binding levels set to simulate the concurrent
1989
+        # port binding operations fail
1990
+        with mock.patch(
1991
+            'neutron.plugins.ml2.plugin.Ml2Plugin._bind_port',
1992
+            return_value=bound_context),\
1993
+            mock.patch('neutron.plugins.ml2.plugin.Ml2Plugin.'
1994
+                       '_notify_port_updated') as npu_mock,\
1995
+            mock.patch('neutron.plugins.ml2.plugin.Ml2Plugin.'
1996
+                       '_attempt_binding',
1997
+                       side_effect=plugin._attempt_binding) as ab_mock,\
1998
+            mock.patch('neutron.plugins.ml2.plugin.Ml2Plugin.'
1999
+                       '_commit_port_binding', return_value=(
2000
+                            mock.MagicMock(), True, True)) as cpb_mock:
2001
+            ret_context = plugin._bind_port_if_needed(port_context,
2002
+                                                      allow_notify=True)
2003
+            # _attempt_binding will return without doing anything during
2004
+            # the second iteration since _should_bind_port returns False
2005
+            self.assertEqual(2, ab_mock.call_count)
2006
+            self.assertEqual(1, cpb_mock.call_count)
2007
+            # _notify_port_updated will still be called though it does
2008
+            # nothing due to the missing binding levels
2009
+            npu_mock.assert_called_once_with(ret_context)
2010
+
2011
+    def test__commit_port_binding_populating_with_binding_levels(self):
2012
+        port_vif_type = portbindings.VIF_TYPE_OVS
2013
+        bound_vif_type = portbindings.VIF_TYPE_OVS
2014
+
2015
+        plugin, port_context, bound_context = (
2016
+            self._create_port_and_bound_context(port_vif_type,
2017
+                                                bound_vif_type))
2018
+        db_portbinding = port_obj.PortBindingLevel(
2019
+            self.context,
2020
+            port_id=uuidutils.generate_uuid(),
2021
+            level=0,
2022
+            driver='fake_agent',
2023
+            segment_id="11111111-2222-3333-4444-555555555555")
2024
+        bound_context.network.current = {'id': 'net_id'}
2025
+
2026
+        with mock.patch.object(ml2_db, 'get_binding_levels',
2027
+                return_value=[db_portbinding]),\
2028
+                mock.patch.object(driver_context.PortContext,
2029
+                '_push_binding_level') as pbl_mock:
2030
+            plugin._commit_port_binding(
2031
+                    port_context, bound_context, True, False)
2032
+            pbl_mock.assert_called_once_with(db_portbinding)
2033
+
1973 2034
     def test_port_binding_profile_not_changed(self):
1974 2035
         profile = {'e': 5}
1975 2036
         profile_arg = {portbindings.PROFILE: profile}

Loading…
Cancel
Save