Skip to content

Commit 347486f

Browse files
bpetermannS11ralonsoh
authored andcommitted
Fix concurrent port binding activate
Fix an issue with concurrent requests to activate a port binding. If there are two activate requests in parallel, one might set the binding on the new host to active and the other request may not find the previously INACTIVE row anymore in _commit_port_binding and initializing the driver_context.PortContext crashed. Closes-Bug: #1986003 Change-Id: I047e33062bc38f36848e0149c6e670cb5828c8e3 (cherry picked from commit 5b4ed5b)
1 parent 9bbb97f commit 347486f

File tree

3 files changed

+36
-0
lines changed

3 files changed

+36
-0
lines changed

neutron/plugins/ml2/plugin.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,14 @@ def _commit_port_binding(self, orig_context, bind_context,
694694
# transaction that completed before the deletion.
695695
LOG.debug("Port %s has been deleted concurrently", port_id)
696696
return orig_context, False, False
697+
698+
if (new_binding.status == const.INACTIVE and
699+
new_binding.host == cur_binding.host):
700+
# The binding is already active on the target host,
701+
# probably because of a concurrent activate request.
702+
raise exc.PortBindingAlreadyActive(port_id=port_id,
703+
host=new_binding.host)
704+
697705
# Since the mechanism driver bind_port() calls must be made
698706
# outside a DB transaction locking the port state, it is
699707
# possible (but unlikely) that the port's state could change

neutron/tests/unit/plugins/ml2/test_port_binding.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# License for the specific language governing permissions and limitations
1414
# under the License.
1515

16+
from concurrent import futures
1617
from unittest import mock
1718

1819
from neutron_lib.api.definitions import portbindings
@@ -561,6 +562,23 @@ def test_activate_port_binding(self):
561562
retrieved_bindings, const.INACTIVE)
562563
self._assert_unbound_port_binding(retrieved_inactive_binding)
563564

565+
def test_activate_port_binding_concurrency(self):
566+
port, _ = self._create_port_and_binding()
567+
with mock.patch.object(mechanism_test.TestMechanismDriver,
568+
'_check_port_context'):
569+
with futures.ThreadPoolExecutor() as executor:
570+
f1 = executor.submit(
571+
self._activate_port_binding, port['id'], self.host)
572+
f2 = executor.submit(
573+
self._activate_port_binding, port['id'], self.host)
574+
result_1 = f1.result()
575+
result_2 = f2.result()
576+
577+
# One request should be successful and the other should receive a
578+
# HTTPConflict. The order is arbitrary.
579+
self.assertEqual({webob.exc.HTTPConflict.code, webob.exc.HTTPOk.code},
580+
{result_1.status_int, result_2.status_int})
581+
564582
def test_activate_port_binding_for_non_compute_owner(self):
565583
port, new_binding = self._create_port_and_binding()
566584
data = {'port': {'device_owner': ''}}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
fixes:
3+
- |
4+
`1986003 <https://bugs.launchpad.net/neutron/+bug/1986003>`_
5+
Fixed an issue with concurrent requests to activate the same port binding
6+
where one of the requests returned a 500 Internal Server Error.
7+
With the fix one request will return successfully and the other will
8+
return a 409 Conflict (Binding already active).
9+
This fixes errors in nova live-migrations where those concurrent requests
10+
might be sent. Nova handles the 409/Conflict response gracefully.

0 commit comments

Comments
 (0)