Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions neutron/plugins/ml2/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,14 @@ def _commit_port_binding(self, orig_context, bind_context,
# 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
Expand Down
18 changes: 18 additions & 0 deletions neutron/tests/unit/plugins/ml2/test_port_binding.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 portbindings
Expand Down Expand Up @@ -561,6 +562,23 @@ def test_activate_port_binding(self):
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': ''}}
Expand Down
10 changes: 10 additions & 0 deletions releasenotes/notes/bug-1986003-9bf5ca04f9304336.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
fixes:
- |
`1986003 <https://bugs.launchpad.net/neutron/+bug/1986003>`_
Fixed an issue with concurrent requests to activate the same port binding
where one of the requests returned a 500 Internal Server Error.
With the fix one request will return successfully and the other will
return a 409 Conflict (Binding already active).
This fixes errors in nova live-migrations where those concurrent requests
might be sent. Nova handles the 409/Conflict response gracefully.