Add "activate_port_binding" neutron API method

This method will be needed to activate the inactive
port binding for the destination host during post-live-migration.

This is based on the neutron API spec:

https://specs.openstack.org/openstack/neutron-specs/specs/backlog/pike/portbinding_information_for_nova.html#activating-an-inactive-binding

And neutron ML2 plugin API code:

https://review.openstack.org/414251/

Part of blueprint neutron-new-port-binding-api

Change-Id: Ic9fe5ebd3433073b51e95b62867d5c1cd5f2cc8f
This commit is contained in:
Matt Riedemann 2018-03-23 17:44:59 -04:00
parent 19c334b1d7
commit c5356cb3d8
3 changed files with 80 additions and 0 deletions

View File

@ -880,6 +880,11 @@ class PortBindingDeletionFailed(NovaException):
"%(host)s.")
class PortBindingActivationFailed(NovaException):
msg_fmt = _("Failed to activate binding for port %(port_id)s and host "
"%(host)s.")
class PortUpdateFailed(Invalid):
msg_fmt = _("Port update failed for port %(port_id)s: %(reason)s")

View File

@ -1284,6 +1284,44 @@ class API(base_api.NetworkAPI):
raise exception.PortBindingDeletionFailed(
port_id=port_id, host=host)
def activate_port_binding(self, context, port_id, host):
"""Activates an inactive port binding.
If there are two port bindings to different hosts, activating the
inactive binding atomically changes the other binding to inactive.
:param context: The request context for the operation.
:param port_id: The ID of the port with an inactive binding on the
host.
:param host: The host on which the inactive port binding should be
activated.
:raises: nova.exception.PortBindingActivationFailed if a non-409 error
response is received from neutron.
"""
client = _get_ksa_client(context, admin=True)
# This is a bit weird in that we don't PUT and update the status
# to ACTIVE, it's more like a POST action method in the compute API.
resp = client.put(
'/v2.0/ports/%s/bindings/%s/activate' % (port_id, host),
raise_exc=False)
if resp:
LOG.debug('Activated binding for port %s and host %s.',
port_id, host)
else:
# A 409 means the port binding is already active, which shouldn't
# happen if the caller is doing things in the correct order.
if resp.status_code == 409:
LOG.warning('Binding for port %s and host %s is already '
'active.', port_id, host)
else:
# Log the details, raise an exception.
LOG.error('Unexpected error trying to activate binding '
'for port %s and host %s. Code: %s. '
'Error: %s', port_id, host, resp.status_code,
resp.text)
raise exception.PortBindingActivationFailed(
port_id=port_id, host=host)
def _get_pci_device_profile(self, pci_dev):
dev_spec = self.pci_whitelist.get_devspec(pci_dev)
if dev_spec:

View File

@ -5464,6 +5464,43 @@ class TestPortBindingWithMock(test.NoDBTestCase):
else:
self.api.delete_port_binding(ctxt, port_id, 'fake-host')
@mock.patch('nova.network.neutronv2.api._get_ksa_client')
def test_activate_port_binding(self, mock_client):
"""Tests the happy path of activating an inactive port binding."""
ctxt = context.get_context()
resp = fake_req.FakeResponse(200)
mock_client.return_value.put.return_value = resp
self.api.activate_port_binding(ctxt, uuids.port_id, 'fake-host')
mock_client.return_value.put.assert_called_once_with(
'/v2.0/ports/%s/bindings/fake-host/activate' % uuids.port_id,
raise_exc=False)
@mock.patch('nova.network.neutronv2.api._get_ksa_client')
@mock.patch('nova.network.neutronv2.api.LOG.warning')
def test_activate_port_binding_already_active(
self, mock_log_warning, mock_client):
"""Tests the 409 case of activating an already active port binding."""
ctxt = context.get_context()
mock_client.return_value.put.return_value = fake_req.FakeResponse(409)
self.api.activate_port_binding(ctxt, uuids.port_id, 'fake-host')
mock_client.return_value.put.assert_called_once_with(
'/v2.0/ports/%s/bindings/fake-host/activate' % uuids.port_id,
raise_exc=False)
self.assertEqual(1, mock_log_warning.call_count)
self.assertIn('is already active', mock_log_warning.call_args[0][0])
@mock.patch('nova.network.neutronv2.api._get_ksa_client')
def test_activate_port_binding_fails(self, mock_client):
"""Tests the unknown error case of binding activation."""
ctxt = context.get_context()
mock_client.return_value.put.return_value = fake_req.FakeResponse(500)
self.assertRaises(exception.PortBindingActivationFailed,
self.api.activate_port_binding,
ctxt, uuids.port_id, 'fake-host')
mock_client.return_value.put.assert_called_once_with(
'/v2.0/ports/%s/bindings/fake-host/activate' % uuids.port_id,
raise_exc=False)
class TestAllocateForInstance(test.NoDBTestCase):
def setUp(self):