Use neutronclient's port binding APIs
Take advantage of the neutronclient bindings for the port binding APIs added in neutronclient 7.1.0 to avoid having to vendor this stuff ourselves. Change-Id: Icc284203fb53658abe304f24a62705217f90b22b Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
parent
9cdecc81fb
commit
982e2ee02d
@ -122,7 +122,7 @@ python-glanceclient==2.8.0
|
||||
python-ironicclient==3.0.0
|
||||
python-keystoneclient==3.15.0
|
||||
python-mimeparse==1.6.0
|
||||
python-neutronclient==6.7.0
|
||||
python-neutronclient==7.1.0
|
||||
python-subunit==1.4.0
|
||||
pytz==2018.3
|
||||
PyYAML==5.1
|
||||
|
@ -816,13 +816,13 @@ class PortBindingFailed(Invalid):
|
||||
|
||||
|
||||
class PortBindingDeletionFailed(NovaException):
|
||||
msg_fmt = _("Failed to delete binding for port %(port_id)s and host "
|
||||
"%(host)s.")
|
||||
msg_fmt = _("Failed to delete binding for port(s) %(port_id)s on host "
|
||||
"%(host)s; please check neutron logs for more information")
|
||||
|
||||
|
||||
class PortBindingActivationFailed(NovaException):
|
||||
msg_fmt = _("Failed to activate binding for port %(port_id)s and host "
|
||||
"%(host)s.")
|
||||
msg_fmt = _("Failed to activate binding for port %(port_id)s on host "
|
||||
"%(host)s; please check neutron logs for more information")
|
||||
|
||||
|
||||
class PortUpdateFailed(Invalid):
|
||||
|
@ -255,26 +255,6 @@ def get_client(context, admin=False):
|
||||
admin=admin or context.is_admin)
|
||||
|
||||
|
||||
def _get_ksa_client(context, admin=False):
|
||||
"""Returns a keystoneauth Adapter
|
||||
|
||||
This method should only be used if python-neutronclient does not yet
|
||||
provide the necessary API bindings.
|
||||
|
||||
:param context: User request context
|
||||
:param admin: If True, uses the configured credentials, else uses the
|
||||
existing auth_token in the context (the user token).
|
||||
:returns: keystoneauth1 Adapter object
|
||||
"""
|
||||
auth_plugin = _get_auth_plugin(context, admin=admin)
|
||||
session = _get_session()
|
||||
client = utils.get_ksa_adapter(
|
||||
'network', ksa_auth=auth_plugin, ksa_session=session)
|
||||
client.additional_headers = {'accept': 'application/json'}
|
||||
client.connect_retries = CONF.neutron.http_retries
|
||||
return client
|
||||
|
||||
|
||||
def _is_not_duplicate(item, items, items_list_name, instance):
|
||||
present = item in items
|
||||
|
||||
@ -417,25 +397,29 @@ class API:
|
||||
:param host: host from which to delete port bindings
|
||||
:raises: PortBindingDeletionFailed if port binding deletion fails.
|
||||
"""
|
||||
client = get_client(context, admin=True)
|
||||
failed_port_ids = []
|
||||
|
||||
for port in ports:
|
||||
# This call is safe in that 404s for non-existing
|
||||
# bindings are ignored.
|
||||
try:
|
||||
self.delete_port_binding(
|
||||
context, port['id'], host)
|
||||
except exception.PortBindingDeletionFailed:
|
||||
# delete_port_binding will log an error for each
|
||||
# failure but since we're iterating a list we want
|
||||
# to keep track of all failures to build a generic
|
||||
# exception to raise
|
||||
client.delete_port_binding(port['id'], host)
|
||||
except neutron_client_exc.NeutronClientException as exc:
|
||||
# We can safely ignore 404s since we're trying to delete
|
||||
# the thing that wasn't found anyway, but for everything else
|
||||
# we should log an error
|
||||
if exc.status_code == 404:
|
||||
continue
|
||||
|
||||
failed_port_ids.append(port['id'])
|
||||
LOG.exception(
|
||||
"Failed to delete binding for port %(port_id)s on host "
|
||||
"%(host)s", {'port_id': port['id'], 'host': host})
|
||||
|
||||
if failed_port_ids:
|
||||
msg = (_("Failed to delete binding for port(s) "
|
||||
"%(port_ids)s and host %(host)s.") %
|
||||
{'port_ids': ','.join(failed_port_ids),
|
||||
'host': host})
|
||||
raise exception.PortBindingDeletionFailed(msg)
|
||||
raise exception.PortBindingDeletionFailed(
|
||||
port_id=','.join(failed_port_ids), host=host)
|
||||
|
||||
def _get_available_networks(self, context, project_id,
|
||||
net_ids=None, neutron=None,
|
||||
@ -1329,9 +1313,9 @@ class API:
|
||||
LOG.debug('Instance does not have any ports.', instance=instance)
|
||||
return {}
|
||||
|
||||
client = _get_ksa_client(context, admin=True)
|
||||
client = get_client(context, admin=True)
|
||||
|
||||
bindings_by_port_id = {}
|
||||
bindings_by_port_id: ty.Dict[str, ty.Any] = {}
|
||||
for vif in network_info:
|
||||
# Now bind each port to the destination host and keep track of each
|
||||
# port that is bound to the resulting binding so we can rollback in
|
||||
@ -1348,46 +1332,28 @@ class API:
|
||||
else:
|
||||
binding['profile'] = port_profiles[port_id]
|
||||
|
||||
data = dict(binding=binding)
|
||||
resp = self._create_port_binding(context, client, port_id, data)
|
||||
if resp:
|
||||
bindings_by_port_id[port_id] = resp.json()['binding']
|
||||
else:
|
||||
data = {'binding': binding}
|
||||
try:
|
||||
binding = client.create_port_binding(port_id, data)['binding']
|
||||
except neutron_client_exc.NeutronClientException:
|
||||
# Something failed, so log the error and rollback any
|
||||
# successful bindings.
|
||||
LOG.error('Binding failed for port %s and host %s. '
|
||||
'Error: (%s %s)',
|
||||
port_id, host, resp.status_code, resp.text,
|
||||
instance=instance)
|
||||
LOG.error('Binding failed for port %s and host %s.',
|
||||
port_id, host, instance=instance, exc_info=True)
|
||||
for rollback_port_id in bindings_by_port_id:
|
||||
try:
|
||||
self.delete_port_binding(
|
||||
context, rollback_port_id, host)
|
||||
except exception.PortBindingDeletionFailed:
|
||||
LOG.warning('Failed to remove binding for port %s on '
|
||||
'host %s.', rollback_port_id, host,
|
||||
instance=instance)
|
||||
client.delete_port_binding(rollback_port_id, host)
|
||||
except neutron_client_exc.NeutronClientException as exc:
|
||||
if exc.status_code != 404:
|
||||
LOG.warning('Failed to remove binding for port %s '
|
||||
'on host %s.', rollback_port_id, host,
|
||||
instance=instance)
|
||||
raise exception.PortBindingFailed(port_id=port_id)
|
||||
|
||||
bindings_by_port_id[port_id] = binding
|
||||
|
||||
return bindings_by_port_id
|
||||
|
||||
@staticmethod
|
||||
def _create_port_binding(context, client, port_id, data):
|
||||
"""Creates a port binding with the specified data.
|
||||
|
||||
:param context: The request context for the operation.
|
||||
:param client: keystoneauth1.adapter.Adapter
|
||||
:param port_id: The ID of the port on which to create the binding.
|
||||
:param data: dict of port binding data (requires at least the host),
|
||||
for example::
|
||||
|
||||
{'binding': {'host': 'dest.host.com'}}
|
||||
:return: requests.Response object
|
||||
"""
|
||||
return client.post(
|
||||
'/v2.0/ports/%s/bindings' % port_id, json=data, raise_exc=False,
|
||||
global_request_id=context.global_id)
|
||||
|
||||
def delete_port_binding(self, context, port_id, host):
|
||||
"""Delete the port binding for the given port ID and host
|
||||
|
||||
@ -1400,104 +1366,19 @@ class API:
|
||||
:raises: nova.exception.PortBindingDeletionFailed if a non-404 error
|
||||
response is received from neutron.
|
||||
"""
|
||||
client = _get_ksa_client(context, admin=True)
|
||||
resp = self._delete_port_binding(context, client, port_id, host)
|
||||
if resp:
|
||||
LOG.debug('Deleted binding for port %s and host %s.',
|
||||
port_id, host)
|
||||
else:
|
||||
client = get_client(context, admin=True)
|
||||
try:
|
||||
client.delete_port_binding(port_id, host)
|
||||
except neutron_client_exc.NeutronClientException as exc:
|
||||
# We can safely ignore 404s since we're trying to delete
|
||||
# the thing that wasn't found anyway.
|
||||
if resp.status_code != 404:
|
||||
# Log the details, raise an exception.
|
||||
LOG.error('Unexpected error trying to delete binding '
|
||||
'for port %s and host %s. Code: %s. '
|
||||
'Error: %s', port_id, host,
|
||||
resp.status_code, resp.text)
|
||||
if exc.status_code != 404:
|
||||
LOG.error(
|
||||
'Unexpected error trying to delete binding for port %s '
|
||||
'and host %s.', port_id, host, exc_info=True)
|
||||
raise exception.PortBindingDeletionFailed(
|
||||
port_id=port_id, host=host)
|
||||
|
||||
@staticmethod
|
||||
def _delete_port_binding(context, client, port_id, host):
|
||||
"""Deletes the binding for the given host on the given port.
|
||||
|
||||
:param context: The request context for the operation.
|
||||
:param client: keystoneauth1.adapter.Adapter
|
||||
:param port_id: ID of the port from which to delete the binding
|
||||
:param host: A string name of the host on which the port is bound
|
||||
:return: requests.Response object
|
||||
"""
|
||||
return client.delete(
|
||||
'/v2.0/ports/%s/bindings/%s' % (port_id, host), raise_exc=False,
|
||||
global_request_id=context.global_id)
|
||||
|
||||
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 = self._activate_port_binding(context, client, port_id, host)
|
||||
if resp:
|
||||
LOG.debug('Activated binding for port %s and host %s.',
|
||||
port_id, host)
|
||||
# A 409 means the port binding is already active, which shouldn't
|
||||
# happen if the caller is doing things in the correct order.
|
||||
elif 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)
|
||||
|
||||
@staticmethod
|
||||
def _activate_port_binding(context, client, port_id, host):
|
||||
"""Activates an inactive port binding.
|
||||
|
||||
:param context: The request context for the operation.
|
||||
:param client: keystoneauth1.adapter.Adapter
|
||||
:param port_id: ID of the port to activate the binding on
|
||||
:param host: A string name of the host identifying the binding to be
|
||||
activated
|
||||
:return: requests.Response object
|
||||
"""
|
||||
return client.put(
|
||||
'/v2.0/ports/%s/bindings/%s/activate' % (port_id, host),
|
||||
raise_exc=False,
|
||||
global_request_id=context.global_id)
|
||||
|
||||
@staticmethod
|
||||
def _get_port_binding(context, client, port_id, host):
|
||||
"""Returns a port binding of a given port on a given host
|
||||
|
||||
:param context: The request context for the operation.
|
||||
:param client: keystoneauth1.adapter.Adapter
|
||||
:param port_id: ID of the port to get the binding
|
||||
:param host: A string name of the host identifying the binding to be
|
||||
returned
|
||||
:return: requests.Response object
|
||||
"""
|
||||
return client.get(
|
||||
'/v2.0/ports/%s/bindings/%s' % (port_id, host),
|
||||
raise_exc=False,
|
||||
global_request_id=context.global_id)
|
||||
|
||||
def _get_pci_device_profile(self, pci_dev):
|
||||
dev_spec = self.pci_whitelist.get_devspec(pci_dev)
|
||||
if dev_spec:
|
||||
@ -2865,43 +2746,73 @@ class API:
|
||||
'updated later.', instance=instance)
|
||||
return
|
||||
|
||||
client = _get_ksa_client(context, admin=True)
|
||||
client = get_client(context, admin=True)
|
||||
dest_host = migration['dest_compute']
|
||||
for vif in instance.get_network_info():
|
||||
# Not all compute migration flows use the port binding-extended
|
||||
# API yet, so first check to see if there is a binding for the
|
||||
# port and destination host.
|
||||
resp = self._get_port_binding(
|
||||
context, client, vif['id'], dest_host)
|
||||
if resp:
|
||||
if resp.json()['binding']['status'] != 'ACTIVE':
|
||||
self.activate_port_binding(context, vif['id'], dest_host)
|
||||
# TODO(mriedem): Do we need to call
|
||||
# _clear_migration_port_profile? migrate_instance_finish
|
||||
# would normally take care of clearing the "migrating_to"
|
||||
# attribute on each port when updating the port's
|
||||
# binding:host_id to point to the destination host.
|
||||
else:
|
||||
# We might be racing with another thread that's handling
|
||||
# post-migrate operations and already activated the port
|
||||
# binding for the destination host.
|
||||
LOG.debug('Port %s binding to destination host %s is '
|
||||
'already ACTIVE.', vif['id'], dest_host,
|
||||
instance=instance)
|
||||
elif resp.status_code == 404:
|
||||
# If there is no port binding record for the destination host,
|
||||
# we can safely assume none of the ports attached to the
|
||||
try:
|
||||
binding = client.show_port_binding(
|
||||
vif['id'], dest_host
|
||||
)['binding']
|
||||
except neutron_client_exc.NeutronClientException as exc:
|
||||
if exc.status_code != 404:
|
||||
# We don't raise an exception here because we assume that
|
||||
# port bindings will be updated correctly when
|
||||
# migrate_instance_finish runs
|
||||
LOG.error(
|
||||
'Unexpected error trying to get binding info '
|
||||
'for port %s and destination host %s.',
|
||||
vif['id'], dest_host, exc_info=True)
|
||||
continue
|
||||
|
||||
# ...but if there is no port binding record for the destination
|
||||
# host, we can safely assume none of the ports attached to the
|
||||
# instance are using the binding-extended API in this flow and
|
||||
# exit early.
|
||||
return
|
||||
else:
|
||||
# We don't raise an exception here because we assume that
|
||||
# port bindings will be updated correctly when
|
||||
# migrate_instance_finish runs.
|
||||
LOG.error('Unexpected error trying to get binding info '
|
||||
'for port %s and destination host %s. Code: %i. '
|
||||
'Error: %s', vif['id'], dest_host, resp.status_code,
|
||||
resp.text)
|
||||
|
||||
if binding['status'] == 'ACTIVE':
|
||||
# We might be racing with another thread that's handling
|
||||
# post-migrate operations and already activated the port
|
||||
# binding for the destination host.
|
||||
LOG.debug(
|
||||
'Port %s binding to destination host %s is already ACTIVE',
|
||||
vif['id'], dest_host, instance=instance)
|
||||
continue
|
||||
|
||||
try:
|
||||
# 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.
|
||||
client.activate_port_binding(vif['id'], dest_host)
|
||||
LOG.debug(
|
||||
'Activated binding for port %s and host %s',
|
||||
vif['id'], dest_host)
|
||||
except neutron_client_exc.NeutronClientException as exc:
|
||||
# A 409 means the port binding is already active, which
|
||||
# shouldn't happen if the caller is doing things in the correct
|
||||
# order.
|
||||
if exc.status_code == 409:
|
||||
LOG.warning(
|
||||
'Binding for port %s and host %s is already active',
|
||||
vif['id'], dest_host, exc_info=True)
|
||||
continue
|
||||
|
||||
# Log the details, raise an exception.
|
||||
LOG.error(
|
||||
'Unexpected error trying to activate binding '
|
||||
'for port %s and host %s.',
|
||||
vif['id'], dest_host, exc_info=True)
|
||||
raise exception.PortBindingActivationFailed(
|
||||
port_id=vif['id'], host=dest_host)
|
||||
|
||||
# TODO(mriedem): Do we need to call
|
||||
# _clear_migration_port_profile? migrate_instance_finish
|
||||
# would normally take care of clearing the "migrating_to"
|
||||
# attribute on each port when updating the port's
|
||||
# binding:host_id to point to the destination host.
|
||||
|
||||
def migrate_instance_finish(
|
||||
self, context, instance, migration, provider_mappings):
|
||||
|
89
nova/tests/fixtures/neutron.py
vendored
89
nova/tests/fixtures/neutron.py
vendored
@ -15,18 +15,14 @@ import copy
|
||||
import random
|
||||
|
||||
import fixtures
|
||||
from keystoneauth1 import adapter as ksa_adap
|
||||
import mock
|
||||
from neutronclient.common import exceptions as neutron_client_exc
|
||||
import os_resource_classes as orc
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from nova import exception
|
||||
from nova.network import constants as neutron_constants
|
||||
from nova.network import model as network_model
|
||||
from nova.tests.fixtures import nova as nova_fixtures
|
||||
from nova.tests.unit import fake_requests
|
||||
|
||||
|
||||
class _FakeNeutronClient:
|
||||
@ -665,25 +661,6 @@ class NeutronFixture(fixtures.Fixture):
|
||||
lambda *args, **kwargs: network_model.NetworkInfo.hydrate(
|
||||
self.nw_info))
|
||||
|
||||
# Stub out port binding APIs which go through a KSA client Adapter
|
||||
# rather than python-neutronclient.
|
||||
self.test.stub_out(
|
||||
'nova.network.neutron._get_ksa_client',
|
||||
lambda *args, **kwargs: mock.Mock(
|
||||
spec=ksa_adap.Adapter))
|
||||
self.test.stub_out(
|
||||
'nova.network.neutron.API._create_port_binding',
|
||||
self.create_port_binding)
|
||||
self.test.stub_out(
|
||||
'nova.network.neutron.API._delete_port_binding',
|
||||
self.delete_port_binding)
|
||||
self.test.stub_out(
|
||||
'nova.network.neutron.API._activate_port_binding',
|
||||
self.activate_port_binding)
|
||||
self.test.stub_out(
|
||||
'nova.network.neutron.API._get_port_binding',
|
||||
self.get_port_binding)
|
||||
|
||||
self.test.stub_out(
|
||||
'nova.network.neutron.get_client', self._get_client)
|
||||
|
||||
@ -692,13 +669,12 @@ class NeutronFixture(fixtures.Fixture):
|
||||
admin = admin or context.is_admin and not context.auth_token
|
||||
return _FakeNeutronClient(self, admin)
|
||||
|
||||
def create_port_binding(self, context, client, port_id, data):
|
||||
def create_port_binding(self, port_id, body):
|
||||
if port_id not in self._ports:
|
||||
return fake_requests.FakeResponse(
|
||||
404, content='Port %s not found' % port_id)
|
||||
raise neutron_client_exc.NeutronClientException(status_code=404)
|
||||
|
||||
port = self._ports[port_id]
|
||||
binding = copy.deepcopy(data['binding'])
|
||||
binding = copy.deepcopy(body['binding'])
|
||||
|
||||
# NOTE(stephenfin): We don't allow changing of backend
|
||||
binding['vif_type'] = port['binding:vif_type']
|
||||
@ -713,61 +689,36 @@ class NeutronFixture(fixtures.Fixture):
|
||||
|
||||
self._port_bindings[port_id][binding['host']] = binding
|
||||
|
||||
return fake_requests.FakeResponse(
|
||||
200, content=jsonutils.dumps({'binding': binding}),
|
||||
)
|
||||
return {'binding': binding}
|
||||
|
||||
def _get_failure_response_if_port_or_binding_not_exists(
|
||||
self, port_id, host,
|
||||
):
|
||||
def _validate_port_binding(self, port_id, host_id):
|
||||
if port_id not in self._ports:
|
||||
return fake_requests.FakeResponse(
|
||||
404, content='Port %s not found' % port_id)
|
||||
if host not in self._port_bindings[port_id]:
|
||||
return fake_requests.FakeResponse(
|
||||
404,
|
||||
content='Binding for host %s for port %s not found'
|
||||
% (host, port_id))
|
||||
raise neutron_client_exc.NeutronClientException(status_code=404)
|
||||
|
||||
def delete_port_binding(self, context, client, port_id, host):
|
||||
failure = self._get_failure_response_if_port_or_binding_not_exists(
|
||||
port_id, host)
|
||||
if failure is not None:
|
||||
return failure
|
||||
if host_id not in self._port_bindings[port_id]:
|
||||
raise neutron_client_exc.NeutronClientException(status_code=404)
|
||||
|
||||
del self._port_bindings[port_id][host]
|
||||
def delete_port_binding(self, port_id, host_id):
|
||||
self._validate_port_binding(port_id, host_id)
|
||||
del self._port_bindings[port_id][host_id]
|
||||
|
||||
return fake_requests.FakeResponse(204)
|
||||
|
||||
def _activate_port_binding(self, port_id, host):
|
||||
def _activate_port_binding(self, port_id, host_id):
|
||||
# It makes sure that only one binding is active for a port
|
||||
for h, binding in self._port_bindings[port_id].items():
|
||||
if h == host:
|
||||
for host, binding in self._port_bindings[port_id].items():
|
||||
if host == host_id:
|
||||
# NOTE(gibi): neutron returns 409 if this binding is already
|
||||
# active but nova does not depend on this behaviour yet.
|
||||
binding['status'] = 'ACTIVE'
|
||||
else:
|
||||
binding['status'] = 'INACTIVE'
|
||||
|
||||
def activate_port_binding(self, context, client, port_id, host):
|
||||
failure = self._get_failure_response_if_port_or_binding_not_exists(
|
||||
port_id, host)
|
||||
if failure is not None:
|
||||
return failure
|
||||
def activate_port_binding(self, port_id, host_id):
|
||||
self._validate_port_binding(port_id, host_id)
|
||||
self._activate_port_binding(port_id, host_id)
|
||||
|
||||
self._activate_port_binding(port_id, host)
|
||||
|
||||
return fake_requests.FakeResponse(200)
|
||||
|
||||
def get_port_binding(self, context, client, port_id, host):
|
||||
failure = self._get_failure_response_if_port_or_binding_not_exists(
|
||||
port_id, host)
|
||||
if failure is not None:
|
||||
return failure
|
||||
|
||||
binding = {"binding": self._port_bindings[port_id][host]}
|
||||
return fake_requests.FakeResponse(
|
||||
200, content=jsonutils.dumps(binding))
|
||||
def show_port_binding(self, port_id, host_id):
|
||||
self._validate_port_binding(port_id, host_id)
|
||||
return {'binding': self._port_bindings[port_id][host_id]}
|
||||
|
||||
def _list_resource(self, resources, retrieve_all, **_params):
|
||||
# If 'fields' is passed we need to strip that out since it will mess
|
||||
|
@ -12,6 +12,7 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import collections
|
||||
import copy
|
||||
import datetime
|
||||
@ -8383,10 +8384,11 @@ class CrossCellResizeWithQoSPort(PortResourceRequestBasedSchedulingTestBase):
|
||||
server = self._create_server_with_ports_and_check_allocation(
|
||||
non_qos_normal_port, qos_normal_port, qos_sriov_port)
|
||||
|
||||
orig_create_binding = neutronapi.API._create_port_binding
|
||||
orig_create_binding = self.neutron.create_port_binding
|
||||
|
||||
hosts = {
|
||||
'host1': self.compute1_rp_uuid, 'host2': self.compute2_rp_uuid}
|
||||
'host1': self.compute1_rp_uuid, 'host2': self.compute2_rp_uuid,
|
||||
}
|
||||
|
||||
# Add an extra check to our neutron fixture. This check makes sure that
|
||||
# the RP sent in the binding corresponds to host of the binding. In a
|
||||
@ -8394,21 +8396,23 @@ class CrossCellResizeWithQoSPort(PortResourceRequestBasedSchedulingTestBase):
|
||||
# 1907522 showed we fail this check for cross cell migration with qos
|
||||
# ports in a real deployment. So to reproduce that bug we need to have
|
||||
# the same check in our test env too.
|
||||
def spy_on_create_binding(context, client, port_id, data):
|
||||
def spy_on_create_binding(port_id, data):
|
||||
host_rp_uuid = hosts[data['binding']['host']]
|
||||
device_rp_uuid = data['binding']['profile'].get('allocation')
|
||||
if port_id == qos_normal_port['id']:
|
||||
if device_rp_uuid != self.ovs_bridge_rp_per_host[host_rp_uuid]:
|
||||
raise exception.PortBindingFailed(port_id=port_id)
|
||||
elif port_id == qos_sriov_port['id']:
|
||||
if (device_rp_uuid not in
|
||||
self.sriov_dev_rp_per_host[host_rp_uuid].values()):
|
||||
if (
|
||||
device_rp_uuid not in
|
||||
self.sriov_dev_rp_per_host[host_rp_uuid].values()
|
||||
):
|
||||
raise exception.PortBindingFailed(port_id=port_id)
|
||||
|
||||
return orig_create_binding(context, client, port_id, data)
|
||||
return orig_create_binding(port_id, data)
|
||||
|
||||
with mock.patch(
|
||||
'nova.network.neutron.API._create_port_binding',
|
||||
'nova.tests.fixtures.NeutronFixture.create_port_binding',
|
||||
side_effect=spy_on_create_binding, autospec=True
|
||||
):
|
||||
# We expect the migration to fail as the only available target
|
||||
@ -8440,8 +8444,8 @@ class CrossCellResizeWithQoSPort(PortResourceRequestBasedSchedulingTestBase):
|
||||
self._create_networking_rp_tree('host3', self.compute3_rp_uuid)
|
||||
|
||||
with mock.patch(
|
||||
'nova.network.neutron.API._create_port_binding',
|
||||
side_effect=spy_on_create_binding, autospec=True
|
||||
'nova.tests.fixtures.NeutronFixture.create_port_binding',
|
||||
side_effect=spy_on_create_binding, autospec=True
|
||||
):
|
||||
server = self._migrate_server(server)
|
||||
self.assertEqual('host3', server['OS-EXT-SRV-ATTR:host'])
|
||||
|
@ -49,7 +49,6 @@ from nova import policy
|
||||
from nova import service_auth
|
||||
from nova import test
|
||||
from nova.tests.unit import fake_instance
|
||||
from nova.tests.unit import fake_requests as fake_req
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
@ -251,8 +250,6 @@ class TestNeutronClient(test.NoDBTestCase):
|
||||
auth_token='token')
|
||||
cl = neutronapi.get_client(my_context)
|
||||
self.assertEqual(retries, cl.httpclient.connect_retries)
|
||||
kcl = neutronapi._get_ksa_client(my_context)
|
||||
self.assertEqual(retries, kcl.connect_retries)
|
||||
|
||||
|
||||
class TestAPIBase(test.TestCase):
|
||||
@ -4894,24 +4891,23 @@ class TestAPI(TestAPIBase):
|
||||
constants.BINDING_PROFILE: migrate_profile,
|
||||
constants.BINDING_HOST_ID: instance.host}]}
|
||||
self.api.list_ports = mock.Mock(return_value=get_ports)
|
||||
update_port_mock = mock.Mock()
|
||||
get_client_mock.return_value.update_port = update_port_mock
|
||||
with mock.patch.object(self.api, 'delete_port_binding') as del_binding:
|
||||
with mock.patch.object(self.api, 'supports_port_binding_extension',
|
||||
return_value=True):
|
||||
self.api.setup_networks_on_host(self.context,
|
||||
instance,
|
||||
host='new-host',
|
||||
teardown=True)
|
||||
update_port_mock.assert_called_once_with(
|
||||
port_id, {'port': {
|
||||
constants.BINDING_PROFILE: migrate_profile}})
|
||||
del_binding.assert_called_once_with(
|
||||
self.context, port_id, 'new-host')
|
||||
mocked_client = get_client_mock.return_value
|
||||
|
||||
@mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock())
|
||||
with mock.patch.object(self.api, 'supports_port_binding_extension',
|
||||
return_value=True):
|
||||
self.api.setup_networks_on_host(self.context,
|
||||
instance,
|
||||
host='new-host',
|
||||
teardown=True)
|
||||
|
||||
mocked_client.update_port.assert_called_once_with(
|
||||
port_id, {'port': {constants.BINDING_PROFILE: migrate_profile}})
|
||||
mocked_client.delete_port_binding.assert_called_once_with(
|
||||
port_id, 'new-host')
|
||||
|
||||
@mock.patch.object(neutronapi, 'get_client')
|
||||
def test_update_port_profile_for_migration_teardown_true_with_profile_exc(
|
||||
self, get_client_mock):
|
||||
self, get_client_mock):
|
||||
"""Tests that delete_port_binding raises PortBindingDeletionFailed
|
||||
which is raised through to the caller.
|
||||
"""
|
||||
@ -4930,25 +4926,27 @@ class TestAPI(TestAPIBase):
|
||||
constants.BINDING_HOST_ID: instance.host}]}
|
||||
self.api.list_ports = mock.Mock(return_value=get_ports)
|
||||
self.api._clear_migration_port_profile = mock.Mock()
|
||||
with mock.patch.object(
|
||||
self.api, 'delete_port_binding',
|
||||
side_effect=exception.PortBindingDeletionFailed(
|
||||
port_id=uuids.port1, host='new-host')) as del_binding:
|
||||
with mock.patch.object(self.api, 'supports_port_binding_extension',
|
||||
return_value=True):
|
||||
ex = self.assertRaises(
|
||||
exception.PortBindingDeletionFailed,
|
||||
self.api.setup_networks_on_host,
|
||||
self.context, instance, host='new-host', teardown=True)
|
||||
# Make sure both ports show up in the exception message.
|
||||
self.assertIn(uuids.port1, str(ex))
|
||||
self.assertIn(uuids.port2, str(ex))
|
||||
NeutronError = exceptions.NeutronClientException(status_code=500)
|
||||
mocked_client = get_client_mock.return_value
|
||||
mocked_client.delete_port_binding.side_effect = NeutronError
|
||||
|
||||
with mock.patch.object(self.api, 'supports_port_binding_extension',
|
||||
return_value=True):
|
||||
ex = self.assertRaises(
|
||||
exception.PortBindingDeletionFailed,
|
||||
self.api.setup_networks_on_host,
|
||||
self.context, instance, host='new-host', teardown=True)
|
||||
# Make sure both ports show up in the exception message.
|
||||
self.assertIn(uuids.port1, str(ex))
|
||||
self.assertIn(uuids.port2, str(ex))
|
||||
|
||||
self.api._clear_migration_port_profile.assert_called_once_with(
|
||||
self.context, instance, get_client_mock.return_value,
|
||||
get_ports['ports'])
|
||||
del_binding.assert_has_calls([
|
||||
mock.call(self.context, uuids.port1, 'new-host'),
|
||||
mock.call(self.context, uuids.port2, 'new-host')])
|
||||
mocked_client.delete_port_binding.assert_has_calls([
|
||||
mock.call(uuids.port1, 'new-host'),
|
||||
mock.call(uuids.port2, 'new-host'),
|
||||
])
|
||||
|
||||
@mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock())
|
||||
def test_update_port_profile_for_migration_teardown_true_no_profile(
|
||||
@ -6101,7 +6099,7 @@ class TestAPI(TestAPIBase):
|
||||
mock_get_inst.assert_called_once_with(ctxt, instance.uuid)
|
||||
mock_get_map.assert_called_once_with(ctxt, instance.uuid)
|
||||
|
||||
@mock.patch('nova.network.neutron._get_ksa_client',
|
||||
@mock.patch('nova.network.neutron.get_client',
|
||||
new_callable=mock.NonCallableMock) # asserts not called
|
||||
def test_migrate_instance_start_no_binding_ext(self, get_client_mock):
|
||||
"""Tests that migrate_instance_start exits early if neutron doesn't
|
||||
@ -6112,63 +6110,65 @@ class TestAPI(TestAPIBase):
|
||||
self.api.migrate_instance_start(
|
||||
self.context, mock.sentinel.instance, {})
|
||||
|
||||
@mock.patch('nova.network.neutron._get_ksa_client')
|
||||
@mock.patch('nova.network.neutron.get_client')
|
||||
def test_migrate_instance_start_activate(self, get_client_mock):
|
||||
"""Tests the happy path for migrate_instance_start where the binding
|
||||
for the port(s) attached to the instance are activated on the
|
||||
destination host.
|
||||
"""
|
||||
binding = {'binding': {'status': 'INACTIVE'}}
|
||||
resp = fake_req.FakeResponse(200, content=jsonutils.dumps(binding))
|
||||
get_client_mock.return_value.get.return_value = resp
|
||||
mocked_client = get_client_mock.return_value
|
||||
mocked_client.show_port_binding.return_value = binding
|
||||
# Just create a simple instance with a single port.
|
||||
instance = objects.Instance(info_cache=objects.InstanceInfoCache(
|
||||
network_info=model.NetworkInfo([model.VIF(uuids.port_id)])))
|
||||
migration = objects.Migration(
|
||||
source_compute='source', dest_compute='dest')
|
||||
with mock.patch.object(self.api, 'activate_port_binding') as activate:
|
||||
with mock.patch.object(self.api, 'supports_port_binding_extension',
|
||||
return_value=True):
|
||||
self.api.migrate_instance_start(
|
||||
self.context, instance, migration)
|
||||
activate.assert_called_once_with(self.context, uuids.port_id, 'dest')
|
||||
get_client_mock.return_value.get.assert_called_once_with(
|
||||
'/v2.0/ports/%s/bindings/dest' % uuids.port_id, raise_exc=False,
|
||||
global_request_id=self.context.global_id)
|
||||
|
||||
@mock.patch('nova.network.neutron._get_ksa_client')
|
||||
with mock.patch.object(self.api, 'supports_port_binding_extension',
|
||||
return_value=True):
|
||||
self.api.migrate_instance_start(
|
||||
self.context, instance, migration)
|
||||
|
||||
mocked_client.show_port_binding.assert_called_once_with(
|
||||
uuids.port_id, 'dest')
|
||||
mocked_client.activate_port_binding.assert_called_once_with(
|
||||
uuids.port_id, 'dest')
|
||||
|
||||
@mock.patch('nova.network.neutron.get_client')
|
||||
def test_migrate_instance_start_already_active(self, get_client_mock):
|
||||
"""Tests the case that the destination host port binding is already
|
||||
ACTIVE when migrate_instance_start is called so we don't try to
|
||||
activate it again, which would result in a 409 from Neutron.
|
||||
"""
|
||||
binding = {'binding': {'status': 'ACTIVE'}}
|
||||
resp = fake_req.FakeResponse(200, content=jsonutils.dumps(binding))
|
||||
get_client_mock.return_value.get.return_value = resp
|
||||
mocked_client = get_client_mock.return_value
|
||||
mocked_client.show_port_binding.return_value = binding
|
||||
# Just create a simple instance with a single port.
|
||||
instance = objects.Instance(info_cache=objects.InstanceInfoCache(
|
||||
network_info=model.NetworkInfo([model.VIF(uuids.port_id)])))
|
||||
migration = objects.Migration(
|
||||
source_compute='source', dest_compute='dest')
|
||||
with mock.patch.object(self.api, 'activate_port_binding',
|
||||
new_callable=mock.NonCallableMock):
|
||||
with mock.patch.object(self.api, 'supports_port_binding_extension',
|
||||
return_value=True):
|
||||
self.api.migrate_instance_start(
|
||||
self.context, instance, migration)
|
||||
get_client_mock.return_value.get.assert_called_once_with(
|
||||
'/v2.0/ports/%s/bindings/dest' % uuids.port_id, raise_exc=False,
|
||||
global_request_id=self.context.global_id)
|
||||
|
||||
@mock.patch('nova.network.neutron._get_ksa_client')
|
||||
with mock.patch.object(self.api, 'supports_port_binding_extension',
|
||||
return_value=True):
|
||||
self.api.migrate_instance_start(
|
||||
self.context, instance, migration)
|
||||
|
||||
mocked_client.show_port_binding.assert_called_once_with(
|
||||
uuids.port_id, 'dest')
|
||||
mocked_client.activate_port_binding.assert_not_called()
|
||||
|
||||
@mock.patch('nova.network.neutron.get_client')
|
||||
def test_migrate_instance_start_no_bindings(self, get_client_mock):
|
||||
"""Tests the case that migrate_instance_start is running against new
|
||||
enough neutron for the binding-extended API but the ports don't have
|
||||
a binding resource against the destination host, so no activation
|
||||
happens.
|
||||
"""
|
||||
get_client_mock.return_value.get.return_value = (
|
||||
fake_req.FakeResponse(404))
|
||||
NeutronNotFound = exceptions.NeutronClientException(status_code=404)
|
||||
mocked_client = get_client_mock.return_value
|
||||
mocked_client.show_port_binding.side_effect = NeutronNotFound
|
||||
# Create an instance with two ports so we can test the short circuit
|
||||
# when we find that the first port doesn't have a dest host binding.
|
||||
instance = objects.Instance(info_cache=objects.InstanceInfoCache(
|
||||
@ -6176,45 +6176,41 @@ class TestAPI(TestAPIBase):
|
||||
model.VIF(uuids.port1), model.VIF(uuids.port2)])))
|
||||
migration = objects.Migration(
|
||||
source_compute='source', dest_compute='dest')
|
||||
with mock.patch.object(self.api, 'activate_port_binding',
|
||||
new_callable=mock.NonCallableMock):
|
||||
with mock.patch.object(self.api, 'supports_port_binding_extension',
|
||||
return_value=True):
|
||||
self.api.migrate_instance_start(
|
||||
self.context, instance, migration)
|
||||
get_client_mock.return_value.get.assert_called_once_with(
|
||||
'/v2.0/ports/%s/bindings/dest' % uuids.port1, raise_exc=False,
|
||||
global_request_id=self.context.global_id)
|
||||
|
||||
@mock.patch('nova.network.neutron._get_ksa_client')
|
||||
with mock.patch.object(self.api, 'supports_port_binding_extension',
|
||||
return_value=True):
|
||||
self.api.migrate_instance_start(
|
||||
self.context, instance, migration)
|
||||
|
||||
mocked_client.show_port_binding.assert_called_once_with(
|
||||
uuids.port1, 'dest')
|
||||
mocked_client.activate_port_binding.assert_not_called()
|
||||
|
||||
@mock.patch('nova.network.neutron.get_client')
|
||||
def test_migrate_instance_start_get_error(self, get_client_mock):
|
||||
"""Tests the case that migrate_instance_start is running against new
|
||||
enough neutron for the binding-extended API but getting the port
|
||||
binding information results in an error response from neutron.
|
||||
"""
|
||||
get_client_mock.return_value.get.return_value = (
|
||||
fake_req.FakeResponse(500))
|
||||
NeutronError = exceptions.NeutronClientException(status_code=500)
|
||||
mocked_client = get_client_mock.return_value
|
||||
mocked_client.show_port_binding.side_effect = NeutronError
|
||||
instance = objects.Instance(info_cache=objects.InstanceInfoCache(
|
||||
network_info=model.NetworkInfo([
|
||||
model.VIF(uuids.port1), model.VIF(uuids.port2)])))
|
||||
migration = objects.Migration(
|
||||
source_compute='source', dest_compute='dest')
|
||||
with mock.patch.object(self.api, 'activate_port_binding',
|
||||
new_callable=mock.NonCallableMock):
|
||||
with mock.patch.object(self.api, 'supports_port_binding_extension',
|
||||
return_value=True):
|
||||
self.api.migrate_instance_start(
|
||||
self.context, instance, migration)
|
||||
self.assertEqual(2, get_client_mock.return_value.get.call_count)
|
||||
get_client_mock.return_value.get.assert_has_calls([
|
||||
mock.call(
|
||||
'/v2.0/ports/%s/bindings/dest' % uuids.port1,
|
||||
raise_exc=False,
|
||||
global_request_id=self.context.global_id),
|
||||
mock.call(
|
||||
'/v2.0/ports/%s/bindings/dest' % uuids.port2,
|
||||
raise_exc=False,
|
||||
global_request_id=self.context.global_id)])
|
||||
|
||||
with mock.patch.object(self.api, 'supports_port_binding_extension',
|
||||
return_value=True):
|
||||
self.api.migrate_instance_start(
|
||||
self.context, instance, migration)
|
||||
|
||||
self.assertEqual(2, mocked_client.show_port_binding.call_count)
|
||||
mocked_client.show_port_binding.assert_has_calls([
|
||||
mock.call(uuids.port1, 'dest'),
|
||||
mock.call(uuids.port2, 'dest'),
|
||||
])
|
||||
|
||||
@mock.patch('nova.network.neutron.get_client')
|
||||
def test_get_requested_resource_for_instance_no_resource_request(
|
||||
@ -6778,7 +6774,7 @@ class TestAPIPortbinding(TestAPIBase):
|
||||
'fake_host', 'setup_instance_network_on_host',
|
||||
self.context, instance, 'fake_host')
|
||||
|
||||
@mock.patch('nova.network.neutron._get_ksa_client',
|
||||
@mock.patch('nova.network.neutron.get_client',
|
||||
new_callable=mock.NonCallableMock)
|
||||
def test_bind_ports_to_host_no_ports(self, mock_client):
|
||||
self.assertDictEqual({},
|
||||
@ -6787,11 +6783,11 @@ class TestAPIPortbinding(TestAPIBase):
|
||||
objects.Instance(info_cache=None),
|
||||
'fake-host'))
|
||||
|
||||
@mock.patch('nova.network.neutron._get_ksa_client')
|
||||
@mock.patch('nova.network.neutron.get_client')
|
||||
def test_bind_ports_to_host(self, mock_client):
|
||||
"""Tests a single port happy path where everything is successful."""
|
||||
def post_side_effect(*args, **kwargs):
|
||||
self.assertDictEqual(binding, kwargs['json'])
|
||||
def fake_create(port_id, data):
|
||||
self.assertDictEqual(binding, data)
|
||||
return mock.DEFAULT
|
||||
|
||||
nwinfo = model.NetworkInfo([model.VIF(uuids.port)])
|
||||
@ -6801,21 +6797,22 @@ class TestAPIPortbinding(TestAPIBase):
|
||||
binding = {'binding': {'host': 'fake-host',
|
||||
'vnic_type': 'normal',
|
||||
'profile': {'foo': 'bar'}}}
|
||||
mocked_client = mock_client.return_value
|
||||
mocked_client.create_port_binding.return_value = binding
|
||||
mocked_client.create_port_binding.side_effect = fake_create
|
||||
|
||||
resp = fake_req.FakeResponse(200, content=jsonutils.dumps(binding))
|
||||
mock_client.return_value.post.return_value = resp
|
||||
mock_client.return_value.post.side_effect = post_side_effect
|
||||
result = self.api.bind_ports_to_host(
|
||||
ctxt, inst, 'fake-host', {uuids.port: 'normal'},
|
||||
{uuids.port: {'foo': 'bar'}})
|
||||
self.assertEqual(1, mock_client.return_value.post.call_count)
|
||||
|
||||
self.assertEqual(1, mocked_client.create_port_binding.call_count)
|
||||
self.assertDictEqual({uuids.port: binding['binding']}, result)
|
||||
|
||||
@mock.patch('nova.network.neutron._get_ksa_client')
|
||||
@mock.patch('nova.network.neutron.get_client')
|
||||
def test_bind_ports_to_host_with_vif_profile_and_vnic(self, mock_client):
|
||||
"""Tests bind_ports_to_host with default/non-default parameters."""
|
||||
def post_side_effect(*args, **kwargs):
|
||||
self.assertDictEqual(binding, kwargs['json'])
|
||||
def fake_create(port_id, data):
|
||||
self.assertDictEqual(binding, data)
|
||||
return mock.DEFAULT
|
||||
|
||||
ctxt = context.get_context()
|
||||
@ -6828,11 +6825,13 @@ class TestAPIPortbinding(TestAPIBase):
|
||||
binding = {'binding': {'host': 'fake-host',
|
||||
'vnic_type': 'direct',
|
||||
'profile': vif_profile}}
|
||||
resp = fake_req.FakeResponse(200, content=jsonutils.dumps(binding))
|
||||
mock_client.return_value.post.return_value = resp
|
||||
mock_client.return_value.post.side_effect = post_side_effect
|
||||
mocked_client = mock_client.return_value
|
||||
mocked_client.create_port_binding.return_value = binding
|
||||
mocked_client.create_port_binding.side_effect = fake_create
|
||||
|
||||
result = self.api.bind_ports_to_host(ctxt, inst, 'fake-host')
|
||||
self.assertEqual(1, mock_client.return_value.post.call_count)
|
||||
|
||||
self.assertEqual(1, mocked_client.create_port_binding.call_count)
|
||||
self.assertDictEqual({uuids.port: binding['binding']}, result)
|
||||
|
||||
# assert that that if vnic_type and profile are set in VIF object
|
||||
@ -6848,14 +6847,17 @@ class TestAPIPortbinding(TestAPIBase):
|
||||
binding = {'binding': {'host': 'fake-host',
|
||||
'vnic_type': 'direct-overridden',
|
||||
'profile': {'foo': 'overridden'}}}
|
||||
resp = fake_req.FakeResponse(200, content=jsonutils.dumps(binding))
|
||||
mock_client.return_value.post.return_value = resp
|
||||
mocked_client = mock_client.return_value
|
||||
mocked_client.create_port_binding.return_value = binding
|
||||
mocked_client.create_port_binding.side_effect = fake_create
|
||||
|
||||
result = self.api.bind_ports_to_host(
|
||||
ctxt, inst, 'fake-host', vnic_type_per_port, vif_profile_per_port)
|
||||
self.assertEqual(2, mock_client.return_value.post.call_count)
|
||||
|
||||
self.assertEqual(2, mocked_client.create_port_binding.call_count)
|
||||
self.assertDictEqual({uuids.port: binding['binding']}, result)
|
||||
|
||||
@mock.patch('nova.network.neutron._get_ksa_client')
|
||||
@mock.patch('nova.network.neutron.get_client')
|
||||
def test_bind_ports_to_host_rollback(self, mock_client):
|
||||
"""Tests a scenario where an instance has two ports, and binding the
|
||||
first is successful but binding the second fails, so the code will
|
||||
@ -6865,43 +6867,42 @@ class TestAPIPortbinding(TestAPIBase):
|
||||
model.VIF(uuids.ok), model.VIF(uuids.fail)])
|
||||
inst = objects.Instance(
|
||||
info_cache=objects.InstanceInfoCache(network_info=nwinfo))
|
||||
NeutronError = exceptions.NeutronClientException(status_code=500)
|
||||
|
||||
def fake_post(url, *args, **kwargs):
|
||||
if uuids.ok in url:
|
||||
mock_response = fake_req.FakeResponse(
|
||||
200, content='{"binding": {"host": "fake-host"}}')
|
||||
else:
|
||||
mock_response = fake_req.FakeResponse(500, content='error')
|
||||
return mock_response
|
||||
def fake_create(port_id, host):
|
||||
if port_id == uuids.ok:
|
||||
return {'binding': {'host': 'fake-host'}}
|
||||
|
||||
mock_client.return_value.post.side_effect = fake_post
|
||||
with mock.patch.object(self.api, 'delete_port_binding',
|
||||
# This will be logged but not re-raised.
|
||||
side_effect=exception.PortBindingDeletionFailed(
|
||||
port_id=uuids.ok, host='fake-host'
|
||||
)) as mock_delete:
|
||||
self.assertRaises(exception.PortBindingFailed,
|
||||
self.api.bind_ports_to_host,
|
||||
self.context, inst, 'fake-host')
|
||||
# assert that post was called twice and delete once
|
||||
self.assertEqual(2, mock_client.return_value.post.call_count)
|
||||
mock_delete.assert_called_once_with(self.context, uuids.ok,
|
||||
'fake-host')
|
||||
raise NeutronError
|
||||
|
||||
@mock.patch('nova.network.neutron._get_ksa_client')
|
||||
mocked_client = mock_client.return_value
|
||||
mocked_client.create_port_binding.side_effect = fake_create
|
||||
mocked_client.delete_port_binding.side_effect = NeutronError
|
||||
|
||||
self.assertRaises(exception.PortBindingFailed,
|
||||
self.api.bind_ports_to_host,
|
||||
self.context, inst, 'fake-host')
|
||||
|
||||
# assert that create was called twice and delete once
|
||||
self.assertEqual(2, mocked_client.create_port_binding.call_count)
|
||||
self.assertEqual(1, mocked_client.delete_port_binding.call_count)
|
||||
mocked_client.delete_port_binding.assert_called_once_with(
|
||||
uuids.ok, 'fake-host')
|
||||
|
||||
@mock.patch('nova.network.neutron.get_client')
|
||||
def test_delete_port_binding(self, mock_client):
|
||||
# Create three ports where:
|
||||
# - one is successfully unbound
|
||||
# - one is not found
|
||||
# - one fails to be unbound
|
||||
def fake_delete(url, *args, **kwargs):
|
||||
if uuids.ok in url:
|
||||
return fake_req.FakeResponse(204)
|
||||
else:
|
||||
status_code = 404 if uuids.notfound in url else 500
|
||||
return fake_req.FakeResponse(status_code)
|
||||
def fake_delete(port_id, host):
|
||||
if port_id == uuids.ok:
|
||||
return
|
||||
|
||||
mock_client.return_value.delete.side_effect = fake_delete
|
||||
status_code = 404 if port_id == uuids.notfound else 500
|
||||
raise exceptions.NeutronClientException(status_code=status_code)
|
||||
|
||||
mock_client.return_value.delete_port_binding.side_effect = fake_delete
|
||||
for port_id in (uuids.ok, uuids.notfound, uuids.fail):
|
||||
if port_id == uuids.fail:
|
||||
self.assertRaises(exception.PortBindingDeletionFailed,
|
||||
@ -6911,45 +6912,6 @@ class TestAPIPortbinding(TestAPIBase):
|
||||
self.api.delete_port_binding(self.context, port_id,
|
||||
'fake-host')
|
||||
|
||||
@mock.patch('nova.network.neutron._get_ksa_client')
|
||||
def test_activate_port_binding(self, mock_client):
|
||||
"""Tests the happy path of activating an inactive port binding."""
|
||||
resp = fake_req.FakeResponse(200)
|
||||
mock_client.return_value.put.return_value = resp
|
||||
self.api.activate_port_binding(self.context, 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,
|
||||
global_request_id=self.context.global_id)
|
||||
|
||||
@mock.patch('nova.network.neutron._get_ksa_client')
|
||||
@mock.patch('nova.network.neutron.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."""
|
||||
mock_client.return_value.put.return_value = fake_req.FakeResponse(409)
|
||||
self.api.activate_port_binding(self.context, 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,
|
||||
global_request_id=self.context.global_id)
|
||||
self.assertEqual(1, mock_log_warning.call_count)
|
||||
self.assertIn('is already active', mock_log_warning.call_args[0][0])
|
||||
|
||||
@mock.patch('nova.network.neutron._get_ksa_client')
|
||||
def test_activate_port_binding_fails(self, mock_client):
|
||||
"""Tests the unknown error case of binding activation."""
|
||||
mock_client.return_value.put.return_value = fake_req.FakeResponse(500)
|
||||
self.assertRaises(exception.PortBindingActivationFailed,
|
||||
self.api.activate_port_binding,
|
||||
self.context, 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,
|
||||
global_request_id=self.context.global_id)
|
||||
|
||||
|
||||
class TestAllocateForInstance(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
|
@ -26,7 +26,7 @@ iso8601>=0.1.11 # MIT
|
||||
jsonschema>=3.2.0 # MIT
|
||||
python-cinderclient!=4.0.0,>=3.3.0 # Apache-2.0
|
||||
keystoneauth1>=3.16.0 # Apache-2.0
|
||||
python-neutronclient>=6.7.0 # Apache-2.0
|
||||
python-neutronclient>=7.1.0 # Apache-2.0
|
||||
python-glanceclient>=2.8.0 # Apache-2.0
|
||||
requests>=2.25.1 # Apache-2.0
|
||||
stevedore>=1.20.0 # Apache-2.0
|
||||
|
Loading…
x
Reference in New Issue
Block a user