Merge "Implement cleanup_instance_network_on_host for neutron API"

This commit is contained in:
Zuul
2019-12-26 02:04:03 +00:00
committed by Gerrit Code Review
4 changed files with 105 additions and 98 deletions
+30 -33
View File
@@ -4486,20 +4486,16 @@ class ComputeManager(manager.Manager):
:param instance: Instance object that was resized/cold migrated
:param migration: Migration object for the resize/cold migrate
"""
# setup_networks_on_host relies on the instance.host not being the same
# as the host we pass in, so we have to mutate the instance to
# effectively trick this code.
with utils.temporary_mutation(instance, host=migration.dest_compute):
LOG.debug('Deleting port bindings for source host.',
instance=instance)
try:
self.network_api.setup_networks_on_host(
ctxt, instance, host=self.host, teardown=True)
except exception.PortBindingDeletionFailed as e:
# Do not let this stop us from cleaning up since the guest
# is already gone.
LOG.error('Failed to delete port bindings from source host. '
'Error: %s', six.text_type(e), instance=instance)
LOG.debug('Deleting port bindings for source host.',
instance=instance)
try:
self.network_api.cleanup_instance_network_on_host(
ctxt, instance, self.host)
except exception.PortBindingDeletionFailed as e:
# Do not let this stop us from cleaning up since the guest
# is already gone.
LOG.error('Failed to delete port bindings from source host. '
'Error: %s', six.text_type(e), instance=instance)
def _delete_volume_attachments(self, ctxt, bdms):
"""Deletes volume attachment records for the given bdms.
@@ -4600,22 +4596,19 @@ class ComputeManager(manager.Manager):
self.network_api.migrate_instance_start(
ctxt, instance, migration)
# Delete port bindings for the target host. This relies on the
# instance.host not being the same as the host we pass in, so we
# have to mutate the instance to effectively trick this code.
with utils.temporary_mutation(instance, host=migration.source_compute):
LOG.debug('Deleting port bindings for target host %s.',
self.host, instance=instance)
try:
# Note that deleting the destination host port bindings does
# not automatically activate the source host port bindings.
self.network_api.setup_networks_on_host(
ctxt, instance, host=self.host, teardown=True)
except exception.PortBindingDeletionFailed as e:
# Do not let this stop us from cleaning up since the guest
# is already gone.
LOG.error('Failed to delete port bindings from target host. '
'Error: %s', six.text_type(e), instance=instance)
# Delete port bindings for the target host.
LOG.debug('Deleting port bindings for target host %s.',
self.host, instance=instance)
try:
# Note that deleting the destination host port bindings does
# not automatically activate the source host port bindings.
self.network_api.cleanup_instance_network_on_host(
ctxt, instance, self.host)
except exception.PortBindingDeletionFailed as e:
# Do not let this stop us from cleaning up since the guest
# is already gone.
LOG.error('Failed to delete port bindings from target host. '
'Error: %s', six.text_type(e), instance=instance)
# Delete any volume attachments remaining for this target host.
LOG.debug('Deleting volume attachments for target host.',
@@ -6419,9 +6412,13 @@ class ComputeManager(manager.Manager):
self._power_off_instance(context, instance, clean_shutdown)
current_power_state = self._get_power_state(context, instance)
self.network_api.cleanup_instance_network_on_host(context, instance,
instance.host)
# NOTE(mriedem): cleanup_instance_network_on_host assumes there are
# multiple host bindings per port which is not the case with the
# shelve/unshelve flow. Doing so will result in failures to update the
# port binding on unshelve (since we would have deleted it here).
if not utils.is_neutron():
self.network_api.cleanup_instance_network_on_host(
context, instance, instance.host)
network_info = self.network_api.get_instance_nw_info(context, instance)
block_device_info = self._get_instance_block_device_info(context,
+54 -30
View File
@@ -340,32 +340,41 @@ class API(base_api.NetworkAPI):
# the current instance.host.
has_binding_ext = self.supports_port_binding_extension(context)
if port_migrating and has_binding_ext:
# Attempt to delete all port bindings on the host and raise
# any errors at the end.
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
failed_port_ids.append(port['id'])
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)
self._delete_port_bindings(context, ports, host)
elif port_migrating:
# Setup the port profile
self._setup_migration_port_profile(
context, instance, host, admin_client, ports)
def _delete_port_bindings(self, context, ports, host):
"""Attempt to delete all port bindings on the host.
:param context: The user request context.
:param ports: list of port dicts to cleanup; the 'id' field is required
per port dict in the list
:param host: host from which to delete port bindings
:raises: PortBindingDeletionFailed if port binding deletion fails.
"""
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
failed_port_ids.append(port['id'])
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)
def _get_available_networks(self, context, project_id,
net_ids=None, neutron=None,
auto_allocate=False):
@@ -3315,15 +3324,30 @@ class API(base_api.NetworkAPI):
context, instance, host, migration, provider_mappings)
def cleanup_instance_network_on_host(self, context, instance, host):
"""Cleanup network for specified instance on host."""
# TODO(mriedem): This should likely be implemented at least for the
# shelve offload operation because the instance is being removed from
# a compute host, VIFs are unplugged, etc, so the ports should also
# be unbound, albeit still logically attached to the instance (for the
# shelve scenario). If _unbind_ports was going to be leveraged here, it
# would have to be adjusted a bit since it currently clears the
# device_id field on the port which is not what we'd want for shelve.
pass
"""Cleanup network for specified instance on host.
Port bindings for the given host are deleted. The ports associated
with the instance via the port device_id field are left intact.
:param context: The user request context.
:param instance: Instance object with the associated ports
:param host: host from which to delete port bindings
:raises: PortBindingDeletionFailed if port binding deletion fails.
"""
# First check to see if the port binding extension is supported.
if not self.supports_port_binding_extension(context):
LOG.info("Neutron extension '%s' is not supported; not cleaning "
"up port bindings for host %s.",
constants.PORT_BINDING_EXTENDED, host, instance=instance)
return
# Now get the ports associated with the instance. We go directly to
# neutron rather than rely on the info cache just like
# setup_networks_on_host.
search_opts = {'device_id': instance.uuid,
'tenant_id': instance.project_id,
'fields': ['id']} # we only need the port id
data = self.list_ports(context, **search_opts)
self._delete_port_bindings(context, data['ports'], host)
def _get_pci_mapping_for_migration(self, instance, migration):
if not instance.migration_context:
+20 -34
View File
@@ -11033,19 +11033,13 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
"""Happy path test for
_confirm_snapshot_based_resize_delete_port_bindings.
"""
def stub_setup_networks_on_host(ctxt, instance, *args, **kwargs):
# Make sure the instance.host was mutated to point at the dest host
self.assertEqual(self.migration.dest_compute, instance.host)
with mock.patch.object(
self.compute.network_api, 'setup_networks_on_host',
side_effect=stub_setup_networks_on_host) as setup_networks:
self.compute.network_api,
'cleanup_instance_network_on_host') as cleanup_networks:
self.compute._confirm_snapshot_based_resize_delete_port_bindings(
self.context, self.instance, self.migration)
setup_networks.assert_called_once_with(
self.context, self.instance, host=self.compute.host,
teardown=True)
cleanup_networks.assert_called_once_with(
self.context, self.instance, self.compute.host)
def test_confirm_snapshot_based_resize_delete_port_bindings_errors(self):
"""Tests error handling for
@@ -11053,29 +11047,27 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
"""
# PortBindingDeletionFailed will be caught and logged.
with mock.patch.object(
self.compute.network_api, 'setup_networks_on_host',
self.compute.network_api, 'cleanup_instance_network_on_host',
side_effect=exception.PortBindingDeletionFailed(
port_id=uuids.port_id, host=self.compute.host)
) as setup_networks:
) as cleanup_networks:
self.compute._confirm_snapshot_based_resize_delete_port_bindings(
self.context, self.instance, self.migration)
setup_networks.assert_called_once_with(
self.context, self.instance, host=self.compute.host,
teardown=True)
cleanup_networks.assert_called_once_with(
self.context, self.instance, self.compute.host)
self.assertIn('Failed to delete port bindings from source host',
self.stdlog.logger.output)
# Anything else is re-raised.
func = self.compute._confirm_snapshot_based_resize_delete_port_bindings
with mock.patch.object(
self.compute.network_api, 'setup_networks_on_host',
self.compute.network_api, 'cleanup_instance_network_on_host',
side_effect=test.TestingException('neutron down')
) as setup_networks:
) as cleanup_networks:
self.assertRaises(test.TestingException, func,
self.context, self.instance, self.migration)
setup_networks.assert_called_once_with(
self.context, self.instance, host=self.compute.host,
teardown=True)
cleanup_networks.assert_called_once_with(
self.context, self.instance, self.compute.host)
def test_delete_volume_attachments(self):
"""Happy path test for _delete_volume_attachments."""
@@ -11206,15 +11198,6 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
# at the source compute.
self.assertEqual(migration.source_compute, migration.dest_compute)
def stub_setup_networks_on_host(ctxt, instance, *args, **kwargs):
# The instance.host should have been mutated to point at the
# source compute.
self.assertEqual(self.migration.source_compute, instance.host)
# Raise PortBindingDeletionFailed to make sure it's caught and
# logged but not fatal.
raise exception.PortBindingDeletionFailed(port_id=uuids.port_id,
host=self.compute.host)
with test.nested(
mock.patch.object(self.compute, 'network_api'),
mock.patch.object(self.compute, '_get_instance_block_device_info'),
@@ -11227,8 +11210,11 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
):
mock_network_api.migrate_instance_start.side_effect = \
stub_migrate_instance_start
mock_network_api.setup_networks_on_host.side_effect = \
stub_setup_networks_on_host
# Raise PortBindingDeletionFailed to make sure it's caught and
# logged but not fatal.
mock_network_api.cleanup_instance_network_on_host.side_effect = \
exception.PortBindingDeletionFailed(port_id=uuids.port_id,
host=self.compute.host)
# Run the code.
self.compute._revert_snapshot_based_resize_at_dest(
self.context, self.instance, self.migration)
@@ -11243,9 +11229,9 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
block_device_info=mock_get_bdi.return_value)
mock_network_api.migrate_instance_start.assert_called_once_with(
self.context, self.instance, self.migration)
mock_network_api.setup_networks_on_host.assert_called_once_with(
self.context, self.instance, host=self.compute.host,
teardown=True)
mock_network_api.cleanup_instance_network_on_host.\
assert_called_once_with(
self.context, self.instance, self.compute.host)
# Assert that even though setup_networks_on_host raised
# PortBindingDeletionFailed it was handled and logged.
self.assertIn('Failed to delete port bindings from target host.',
+1 -1
View File
@@ -170,7 +170,7 @@ class ShelveComputeManagerTestCase(test_compute.BaseTestCase):
mock_notify_instance_usage.assert_has_calls(
mock_notify_instance_usage_call_list)
mock_power_off.assert_has_calls(mock_power_off_call_list)
mock_cleanup.assert_has_calls(mock_cleanup_call_list)
mock_cleanup.assert_not_called()
mock_snapshot.assert_called_once_with(self.context, instance,
'fake_image_id', mock.ANY)
mock_get_power_state.assert_has_calls(mock_get_power_state_call_list)