diff options
author | Zuul <zuul@review.opendev.org> | 2023-04-05 19:14:56 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2023-04-05 19:14:56 +0000 |
commit | 208421910d2bb3c71b0947254d5eca1326c184d0 (patch) | |
tree | 1d1d95b8f4e9bbe444b6bfde422967c0dd489f0f /neutron | |
parent | 35dc0830385ace56352b14f425331cf14caf6587 (diff) | |
parent | 5b4ed5b117f2f418d598af20744f571db581e2ae (diff) | |
download | neutron-208421910d2bb3c71b0947254d5eca1326c184d0.tar.gz |
Merge "Fix concurrent port binding activate"
Diffstat (limited to 'neutron')
-rw-r--r-- | neutron/plugins/ml2/plugin.py | 8 | ||||
-rw-r--r-- | neutron/tests/unit/plugins/ml2/test_port_binding.py | 18 |
2 files changed, 26 insertions, 0 deletions
diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index 59e84a1842..2fa769b34d 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -699,6 +699,14 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, # transaction that completed before the deletion. LOG.debug("Port %s has been deleted concurrently", port_id) return orig_context, False, False + + if (new_binding.status == const.INACTIVE and + new_binding.host == cur_binding.host): + # The binding is already active on the target host, + # probably because of a concurrent activate request. + raise exc.PortBindingAlreadyActive(port_id=port_id, + host=new_binding.host) + # Since the mechanism driver bind_port() calls must be made # outside a DB transaction locking the port state, it is # possible (but unlikely) that the port's state could change diff --git a/neutron/tests/unit/plugins/ml2/test_port_binding.py b/neutron/tests/unit/plugins/ml2/test_port_binding.py index bc1f0dc05e..cf5db88eff 100644 --- a/neutron/tests/unit/plugins/ml2/test_port_binding.py +++ b/neutron/tests/unit/plugins/ml2/test_port_binding.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +from concurrent import futures from unittest import mock from neutron_lib.api.definitions import port as port_def @@ -562,6 +563,23 @@ class ExtendedPortBindingTestCase(test_plugin.NeutronDbPluginV2TestCase): retrieved_bindings, const.INACTIVE) self._assert_unbound_port_binding(retrieved_inactive_binding) + def test_activate_port_binding_concurrency(self): + port, _ = self._create_port_and_binding() + with mock.patch.object(mechanism_test.TestMechanismDriver, + '_check_port_context'): + with futures.ThreadPoolExecutor() as executor: + f1 = executor.submit( + self._activate_port_binding, port['id'], self.host) + f2 = executor.submit( + self._activate_port_binding, port['id'], self.host) + result_1 = f1.result() + result_2 = f2.result() + + # One request should be successful and the other should receive a + # HTTPConflict. The order is arbitrary. + self.assertEqual({webob.exc.HTTPConflict.code, webob.exc.HTTPOk.code}, + {result_1.status_int, result_2.status_int}) + def test_activate_port_binding_for_non_compute_owner(self): port, new_binding = self._create_port_and_binding() data = {'port': {'device_owner': ''}} |