Skip to content

Commit ce16399

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Fix concurrent port binding activate" into stable/yoga
2 parents c246913 + 347486f commit ce16399

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)