NUMA live migration support

To enble NUMA-aware live migration, this patch has the following
changes:

* The compute rpcapi gets a new method,
supports_numa_live_migration(), used to decide whether a host can
*send* RPC 5.3 in order to correctly handle a NUMA live migration.

* check_can_live_migrate_destination() in the compute manager receives
new parameters:
 - The migration object created by the superconductor.
 - The limits objects from the scheduler Selection object, passed
   through the superconductor.

* If a migration object is passed to
check_can_live_migrate_destination() and the virt driver (currently on
libvirt) supports it, the destination sets
dst_supports_numa_live_migration in the migrate data.

* If the source virt driver (again, currently only libvirt) supports
it, and if the source compute manager can send RPC 5.3, and if the
dst_supports_numa_live_migration flag is present in the migrate data,
check_can_live_migrate_source() sets src_supports_numa_live_migration
in the migrate_data.

* The previous bullet points make sure that a NUMA live migration is
initiated if and only if:
 - Both source and dest virt drivers support it.
 - The conductor and the source can send RPC 5.3.
 - The destination can receive RPC 5.3.

* compute manager gets a new _live_migration_claim() method, called
from check_can_live_migrate_destination(). If migrate_data contains
src_supports_numa_live_migration, _live_migration_claim() creates a
claim for the live migration and calls the driver's
post_claim_migrate_data() to add the claimed resources information
into migrate_data.

* post_claim_migrate_data() adds dst_numa_info to migrate_data, which
the source uses to update the instance XML.

* During the live migration, the update_available_resources periodic
task no longer ignores instances that are live migrating.

* If the live migration is successful,
post_live_migration_at_destination() applies the migration context to
the instance. Note that resources were already being cleaned up by a
call to update_available_resource().

* If the migration fails, _rollback_live_migration() calls a new RPC
method, drop_move_claim_at_destination(), to drop the move claim. This
is done as an entirely new separate RPC method because
rollback_live_migration_at_destination() is called conditionally and
deals with cleaning up storage and networking.

* is_trackable_migration() is removed from the resource tracker, as
we're now doing a claim for all types of migrations, including live
migrations. The caveat here is that we're only doing a claim for NUMA
live migration, so live migrations without NUMA will not have a claim
associated. However, their resources are tracked in placement, so this
is considered acceptable.

Implements blueprint numa-aware-live-migration
Change-Id: I3c917796cb30d11e7db1e235ac1625d2a743aaa2
This commit is contained in:
Artom Lifshitz 2019-02-03 15:01:18 +00:00
parent 202aae6652
commit b335d0c157
12 changed files with 636 additions and 103 deletions

View File

@ -519,7 +519,7 @@ class ComputeVirtAPI(virtapi.VirtAPI):
class ComputeManager(manager.Manager):
"""Manages the running instances from creation to destruction."""
target = messaging.Target(version='5.2')
target = messaging.Target(version='5.3')
def __init__(self, compute_driver=None, *args, **kwargs):
"""Load configuration options and connect to the hypervisor."""
@ -6444,11 +6444,23 @@ class ComputeManager(manager.Manager):
"""
return self.driver.check_instance_shared_storage_remote(ctxt, data)
def _dest_can_numa_live_migrate(self, dest_check_data, migration):
# TODO(artom) If we have a libvirt driver we expect it to set
# dst_supports_numa_live_migration, but we have to remove it if we
# did not get a migration from the conductor, indicating that it
# cannot send RPC 5.3. This check can be removed in RPC 6.0.
if ('dst_supports_numa_live_migration' in dest_check_data and
dest_check_data.dst_supports_numa_live_migration and
not migration):
delattr(dest_check_data, 'dst_supports_numa_live_migration')
return dest_check_data
@wrap_exception()
@wrap_instance_event(prefix='compute')
@wrap_instance_fault
def check_can_live_migrate_destination(self, ctxt, instance,
block_migration, disk_over_commit):
block_migration, disk_over_commit,
migration=None, limits=None):
"""Check if it is possible to execute live migration.
This runs checks on the destination host, and then calls
@ -6460,6 +6472,8 @@ class ComputeManager(manager.Manager):
if None, calculate it in driver
:param disk_over_commit: if true, allow disk over commit
if None, ignore disk usage checking
:param migration: objects.Migration object for this live migration.
:param limits: objects.SchedulerLimits object for this live migration.
:returns: a LiveMigrateData object (hypervisor-dependent)
"""
src_compute_info = obj_base.obj_to_primitive(
@ -6469,11 +6483,20 @@ class ComputeManager(manager.Manager):
dest_check_data = self.driver.check_can_live_migrate_destination(ctxt,
instance, src_compute_info, dst_compute_info,
block_migration, disk_over_commit)
dest_check_data = self._dest_can_numa_live_migrate(dest_check_data,
migration)
LOG.debug('destination check data is %s', dest_check_data)
try:
migrate_data = self.compute_rpcapi.\
check_can_live_migrate_source(ctxt, instance,
dest_check_data)
migrate_data = self.compute_rpcapi.check_can_live_migrate_source(
ctxt, instance, dest_check_data)
if ('src_supports_numa_live_migration' in migrate_data and
migrate_data.src_supports_numa_live_migration):
migrate_data = self._live_migration_claim(
ctxt, instance, migrate_data, migration, limits)
elif 'dst_supports_numa_live_migration' in dest_check_data:
LOG.info('Destination was ready for NUMA live migration, '
'but source is either too old, or is set to an '
'older upgrade level.', instance=instance)
# Create migrate_data vifs
migrate_data.vifs = \
migrate_data_obj.VIFMigrateData.create_skeleton_migrate_vifs(
@ -6488,6 +6511,59 @@ class ComputeManager(manager.Manager):
dest_check_data)
return migrate_data
def _live_migration_claim(self, ctxt, instance, migrate_data,
migration, limits):
"""Runs on the destination and does a resources claim, if necessary.
Currently, only NUMA live migrations require it.
:param ctxt: Request context
:param instance: The Instance being live migrated
:param migrate_data: The MigrateData object for this live migration
:param migration: The Migration object for this live migration
:param limits: The SchedulerLimits object for this live migration
:returns: migrate_data with dst_numa_info set if necessary
"""
try:
# NOTE(artom) We might have gotten here from _find_destination() in
# the conductor live migrate task. At that point,
# migration.dest_node is not set yet (nor should it be, we're still
# looking for a destination, after all). Therefore, we cannot use
# migration.dest_node here and must use self._get_nodename().
claim = self.rt.live_migration_claim(
ctxt, instance, self._get_nodename(instance), migration,
limits)
LOG.debug('Created live migration claim.', instance=instance)
except exception.ComputeResourcesUnavailable as e:
raise exception.MigrationPreCheckError(
reason=e.format_message())
return self.driver.post_claim_migrate_data(ctxt, instance,
migrate_data, claim)
def _source_can_numa_live_migrate(self, ctxt, dest_check_data,
source_check_data):
# TODO(artom) Our virt driver may have told us that it supports NUMA
# live migration. However, the following other conditions must be met
# for a NUMA live migration to happen:
# 1. We got a True dst_supports_numa_live_migration in
# dest_check_data, indicating that the dest virt driver supports
# NUMA live migration and that the conductor can send RPC 5.3 and
# that the destination compute manager can receive it.
# 2. Ourselves, the source, can send RPC 5.3. There's no
# sentinel/parameter for this, so we just ask our rpcapi directly.
# If any of these are not met, we need to remove the
# src_supports_numa_live_migration flag from source_check_data to avoid
# incorrectly initiating a NUMA live migration.
# All of this can be removed in RPC 6.0/objects 2.0.
can_numa_live_migrate = (
'dst_supports_numa_live_migration' in dest_check_data and
dest_check_data.dst_supports_numa_live_migration and
self.compute_rpcapi.supports_numa_live_migration(ctxt))
if ('src_supports_numa_live_migration' in source_check_data and
source_check_data.src_supports_numa_live_migration and
not can_numa_live_migrate):
delattr(source_check_data, 'src_supports_numa_live_migration')
return source_check_data
@wrap_exception()
@wrap_instance_event(prefix='compute')
@wrap_instance_fault
@ -6512,6 +6588,8 @@ class ComputeManager(manager.Manager):
result = self.driver.check_can_live_migrate_source(ctxt, instance,
dest_check_data,
block_device_info)
result = self._source_can_numa_live_migrate(ctxt, dest_check_data,
result)
LOG.debug('source check data is %s', result)
return result
@ -7151,6 +7229,15 @@ class ComputeManager(manager.Manager):
migrate_data)
if do_cleanup:
# NOTE(artom) By this time post_live_migration_at_destination()
# will have applied the migration context and saved the instance,
# writing a new instance NUMA topology in the process (if the
# intance has one). Here on the source, some drivers will call
# instance.save() in their cleanup() method, which would clobber
# the new instance NUMA topology saved by the destination with the
# old fields in our instance object. To prevent this, refresh our
# instance.
instance.refresh()
LOG.debug('Calling driver.cleanup from _post_live_migration',
instance=instance)
self.driver.cleanup(ctxt, instance, unplug_nw_info,
@ -7270,6 +7357,16 @@ class ComputeManager(manager.Manager):
except exception.ComputeHostNotFound:
LOG.exception('Failed to get compute_info for %s', self.host)
finally:
# NOTE(artom) We need to apply the migration context here
# regardless of whether the driver's
# post_live_migration_at_destination succeeded or not: the
# instance is on the destination, potentially with a new NUMA
# topology and resource usage. We need to persist that.
# NOTE(artom) Apply followed by drop looks weird, but apply
# just saves the new fields while drop actually removes the
# migration context from the instance.
instance.apply_migration_context()
instance.drop_migration_context()
instance.host = self.host
instance.power_state = current_power_state
instance.task_state = None
@ -7340,8 +7437,26 @@ class ComputeManager(manager.Manager):
'rollback; compute driver did not provide migrate_data',
instance=instance)
# TODO(artom) drop_move_claim_at_destination() is new in RPC 5.3, only
# call it if we performed a NUMA-aware live migration (which implies us
# being able to send RPC 5.3). To check this, we can use the
# src_supports_numa_live_migration flag, as it will be set if and only
# if:
# - dst_supports_numa_live_migration made its way to the source
# (meaning both dest and source are new and conductor can speak
# RPC 5.3)
# - src_supports_numa_live_migration was set by the source driver and
# passed the send-RPC-5.3 check.
# This check can be removed in RPC 6.0.
if ('src_supports_numa_live_migration' in migrate_data and
migrate_data.src_supports_numa_live_migration):
LOG.debug('Calling destination to drop move claim.',
instance=instance)
self.compute_rpcapi.drop_move_claim_at_destination(context,
instance, dest)
instance.task_state = None
instance.progress = 0
instance.drop_migration_context()
instance.save(expected_task_state=[task_states.MIGRATING])
# NOTE(tr3buchet): setup networks on source host (really it's re-setup
@ -7429,6 +7544,19 @@ class ComputeManager(manager.Manager):
self._set_migration_status(migration, migration_status)
@wrap_exception()
@wrap_instance_fault
def drop_move_claim_at_destination(self, context, instance):
"""Called by the source of a live migration during rollback to ask the
destination to drop the MoveClaim object that was created for the live
migration on the destination.
"""
nodename = self._get_nodename(instance)
LOG.debug('Dropping live migration resource claim on destination '
'node %s', nodename, instance=instance)
self.rt.drop_move_claim(
context, instance, nodename, instance_type=instance.flavor)
@wrap_exception()
@wrap_instance_event(prefix='compute')
@wrap_instance_fault

View File

@ -73,13 +73,13 @@ def _instance_in_resize_state(instance):
return False
def _is_trackable_migration(migration):
# Only look at resize/migrate migration and evacuation records
# NOTE(danms): RT should probably examine live migration
# records as well and do something smart. However, ignore
# those for now to avoid them being included in below calculations.
return migration.migration_type in ('resize', 'migration',
'evacuation')
def _instance_is_live_migrating(instance):
vm = instance.vm_state
task = instance.task_state
if task == task_states.MIGRATING and vm in [vm_states.ACTIVE,
vm_states.PAUSED]:
return True
return False
def _normalize_inventory_from_cn_obj(inv_data, cn):
@ -1138,9 +1138,6 @@ class ResourceTracker(object):
"""Update usage for a single migration. The record may
represent an incoming or outbound migration.
"""
if not _is_trackable_migration(migration):
return
uuid = migration.instance_uuid
LOG.info("Updating resource usage from migration %s", migration.uuid,
instance_uuid=uuid)
@ -1238,10 +1235,12 @@ class ResourceTracker(object):
LOG.debug('Migration instance not found: %s', e)
continue
# skip migration if instance isn't in a resize state:
if not _instance_in_resize_state(instances[uuid]):
LOG.warning("Instance not resizing, skipping migration.",
instance_uuid=uuid)
# Skip migation if instance is neither in a resize state nor is
# live-migrating.
if (not _instance_in_resize_state(instances[uuid]) and not
_instance_is_live_migrating(instances[uuid])):
LOG.debug('Skipping migration as instance is neither '
'resizing nor live-migrating.', instance_uuid=uuid)
continue
# filter to most recently updated migration for each instance:

View File

@ -367,6 +367,9 @@ class ComputeAPI(object):
* 5.2 - Add request_spec parameter for the following: resize_instance,
finish_resize, revert_resize, finish_revert_resize,
unshelve_instance
* 5.3 - Add migration and limits parameters to
check_can_live_migrate_destination(), and a new
drop_move_claim_at_destination() method
'''
VERSION_ALIASES = {
@ -545,16 +548,25 @@ class ComputeAPI(object):
instance=instance, diff=diff)
def check_can_live_migrate_destination(self, ctxt, instance, destination,
block_migration, disk_over_commit):
version = '5.0'
block_migration, disk_over_commit,
migration, limits):
client = self.router.client(ctxt)
version = '5.3'
kwargs = {
'instance': instance,
'block_migration': block_migration,
'disk_over_commit': disk_over_commit,
'migration': migration,
'limits': limits
}
if not client.can_send_version(version):
kwargs.pop('migration')
kwargs.pop('limits')
version = '5.0'
cctxt = client.prepare(server=destination, version=version,
call_monitor_timeout=CONF.rpc_response_timeout,
timeout=CONF.long_rpc_timeout)
return cctxt.call(ctxt, 'check_can_live_migrate_destination',
instance=instance,
block_migration=block_migration,
disk_over_commit=disk_over_commit)
return cctxt.call(ctxt, 'check_can_live_migrate_destination', **kwargs)
def check_can_live_migrate_source(self, ctxt, instance, dest_check_data):
version = '5.0'
@ -961,6 +973,26 @@ class ComputeAPI(object):
instance=instance, destroy_disks=destroy_disks,
migrate_data=migrate_data)
def supports_numa_live_migration(self, ctxt):
"""Returns whether we can send 5.3, needed for NUMA live migration.
"""
client = self.router.client(ctxt)
return client.can_send_version('5.3')
def drop_move_claim_at_destination(self, ctxt, instance, host):
"""Called by the source of a live migration that's being rolled back.
This is a call not because we care about the return value, but because
dropping the move claim depends on instance.migration_context being
set, and we drop the migration context on the source. Thus, to avoid
races, we call the destination synchronously to make sure it's done
dropping the move claim before we drop the migration context from the
instance.
"""
version = '5.3'
client = self.router.client(ctxt)
cctxt = client.prepare(server=host, version=version)
cctxt.call(ctxt, 'drop_move_claim_at_destination', instance=instance)
def set_admin_password(self, ctxt, instance, new_pass):
version = '5.0'
cctxt = self.router.client(ctxt).prepare(

View File

@ -70,6 +70,7 @@ class LiveMigrationTask(base.TaskBase):
self.migration = migration
self.source = instance.host
self.migrate_data = None
self.limits = None
self.compute_rpcapi = compute_rpcapi
self.servicegroup_api = servicegroup_api
@ -99,7 +100,7 @@ class LiveMigrationTask(base.TaskBase):
# wants the scheduler to pick a destination host, or a host was
# specified but is not forcing it, so they want the scheduler
# filters to run on the specified host, like a scheduler hint.
self.destination, dest_node = self._find_destination()
self.destination, dest_node, self.limits = self._find_destination()
else:
# This is the case that the user specified the 'force' flag when
# live migrating with a specific destination host so the scheduler
@ -319,7 +320,8 @@ class LiveMigrationTask(base.TaskBase):
try:
self.migrate_data = self.compute_rpcapi.\
check_can_live_migrate_destination(self.context, self.instance,
destination, self.block_migration, self.disk_over_commit)
destination, self.block_migration, self.disk_over_commit,
self.migration, self.limits)
except messaging.MessagingTimeout:
msg = _("Timeout while checking if we can live migrate to host: "
"%s") % destination
@ -489,7 +491,9 @@ class LiveMigrationTask(base.TaskBase):
# those before moving on.
self._remove_host_allocations(host, selection.nodename)
host = None
return selection.service_host, selection.nodename
# TODO(artom) We should probably just return the whole selection object
# at this point.
return (selection.service_host, selection.nodename, selection.limits)
def _remove_host_allocations(self, host, node):
"""Removes instance allocations against the given host from Placement

View File

@ -31,7 +31,7 @@ LOG = logging.getLogger(__name__)
# NOTE(danms): This is the global service version counter
SERVICE_VERSION = 39
SERVICE_VERSION = 40
# NOTE(danms): This is our SERVICE_VERSION history. The idea is that any
@ -156,6 +156,11 @@ SERVICE_VERSION_HISTORY = (
# Version 39: resize_instance, finish_resize, revert_resize,
# finish_revert_resize, unshelve_instance takes a RequestSpec object
{'compute_rpc': '5.2'},
# Version 40: Add migration and limits parameters to
# check_can_live_migrate_destination(), new
# drop_move_claim_at_destination() method, and numa_live_migration
# parameter to check_can_live_migrate_source()
{'compute_rpc': '5.3'},
)

View File

@ -6691,7 +6691,9 @@ class ComputeTestCase(BaseTestCase,
c = context.get_admin_context()
instance = mock.MagicMock()
migration = objects.Migration(uuid=uuids.migration)
migrate_data = objects.LibvirtLiveMigrateData(migration=migration)
migrate_data = objects.LibvirtLiveMigrateData(
migration=migration,
src_supports_numa_live_migration=True)
source_bdms = objects.BlockDeviceMappingList()
dest_node = objects.ComputeNode(host='foo', uuid=uuids.dest_node)
@ -6704,7 +6706,10 @@ class ComputeTestCase(BaseTestCase,
@mock.patch.object(self.compute, '_revert_allocation')
@mock.patch.object(self.compute, '_live_migration_cleanup_flags')
@mock.patch.object(self.compute, 'network_api')
def _test(mock_nw_api, mock_lmcf, mock_ra, mock_mig_save, mock_notify):
@mock.patch.object(compute_rpcapi.ComputeAPI,
'drop_move_claim_at_destination')
def _test(mock_drop_claim, mock_nw_api, mock_lmcf, mock_ra,
mock_mig_save, mock_notify):
mock_lmcf.return_value = False, False
if migration_status:
self.compute._rollback_live_migration(
@ -6715,6 +6720,7 @@ class ComputeTestCase(BaseTestCase,
self.compute._rollback_live_migration(
c, instance, 'foo', migrate_data=migrate_data,
source_bdms=source_bdms)
mock_drop_claim.assert_called_once_with(c, instance, 'foo')
mock_notify.assert_has_calls([
mock.call(c, instance, self.compute.host,
action='live_migration_rollback', phase='start',

View File

@ -13,6 +13,7 @@
"""Unit tests for ComputeManager()."""
import contextlib
import copy
import datetime
import time
@ -2678,6 +2679,79 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase,
connector, bdm, uuids.new_attachment_id,
bdm.device_name)
def test_live_migration_claim(self):
mock_claim = mock.Mock()
mock_claim.claimed_numa_topology = mock.sentinel.claimed_topology
stub_image_meta = objects.ImageMeta()
post_claim_md = migrate_data_obj.LiveMigrateData()
with test.nested(
mock.patch.object(self.compute.rt,
'live_migration_claim',
return_value=mock_claim),
mock.patch.object(self.compute, '_get_nodename',
return_value='fake-dest-node'),
mock.patch.object(objects.ImageMeta, 'from_instance',
return_value=stub_image_meta),
mock.patch.object(fake_driver.FakeDriver,
'post_claim_migrate_data',
return_value=post_claim_md)
) as (mock_lm_claim, mock_get_nodename, mock_from_instance,
mock_post_claim_migrate_data):
instance = objects.Instance(flavor=objects.Flavor())
md = objects.LibvirtLiveMigrateData()
migration = objects.Migration()
self.assertEqual(
post_claim_md,
self.compute._live_migration_claim(
self.context, instance, md, migration,
mock.sentinel.limits))
mock_lm_claim.assert_called_once_with(
self.context, instance, 'fake-dest-node', migration,
mock.sentinel.limits)
mock_post_claim_migrate_data.assert_called_once_with(
self.context, instance, md, mock_claim)
def test_live_migration_claim_claim_raises(self):
stub_image_meta = objects.ImageMeta()
with test.nested(
mock.patch.object(
self.compute.rt, 'live_migration_claim',
side_effect=exception.ComputeResourcesUnavailable(
reason='bork')),
mock.patch.object(self.compute, '_get_nodename',
return_value='fake-dest-node'),
mock.patch.object(objects.ImageMeta, 'from_instance',
return_value=stub_image_meta),
mock.patch.object(fake_driver.FakeDriver,
'post_claim_migrate_data')
) as (mock_lm_claim, mock_get_nodename, mock_from_instance,
mock_post_claim_migrate_data):
instance = objects.Instance(flavor=objects.Flavor())
migration = objects.Migration()
self.assertRaises(
exception.MigrationPreCheckError,
self.compute._live_migration_claim,
self.context, instance, objects.LibvirtLiveMigrateData(),
migration, mock.sentinel.limits)
mock_lm_claim.assert_called_once_with(
self.context, instance, 'fake-dest-node', migration,
mock.sentinel.limits)
mock_get_nodename.assert_called_once_with(instance)
mock_post_claim_migrate_data.assert_not_called()
def test_drop_move_claim_at_destination(self):
instance = objects.Instance(flavor=objects.Flavor())
with test.nested(
mock.patch.object(self.compute.rt, 'drop_move_claim'),
mock.patch.object(self.compute, '_get_nodename',
return_value='fake-node')
) as (mock_drop_move_claim, mock_get_nodename):
self.compute.drop_move_claim_at_destination(self.context, instance)
mock_get_nodename.assert_called_once_with(instance)
mock_drop_move_claim.assert_called_once_with(
self.context, instance, 'fake-node',
instance_type=instance.flavor)
@mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid')
@mock.patch.object(fake_driver.FakeDriver,
'check_can_live_migrate_source')
@ -2685,11 +2759,16 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase,
'_get_instance_block_device_info')
@mock.patch.object(compute_utils, 'is_volume_backed_instance')
@mock.patch.object(compute_utils, 'EventReporter')
def test_check_can_live_migrate_source(self, mock_event, mock_volume,
mock_get_inst, mock_check,
mock_get_bdms):
@mock.patch.object(manager.ComputeManager, '_source_can_numa_live_migrate')
def test_check_can_live_migrate_source(
self, mock_src_can_numa, mock_event, mock_volume, mock_get_inst,
mock_check, mock_get_bdms):
fake_bdms = objects.BlockDeviceMappingList()
mock_get_bdms.return_value = fake_bdms
drvr_check_result = migrate_data_obj.LiveMigrateData()
mock_check.return_value = drvr_check_result
can_numa_result = migrate_data_obj.LiveMigrateData()
mock_src_can_numa.return_value = can_numa_result
is_volume_backed = 'volume_backed'
dest_check_data = migrate_data_obj.LiveMigrateData()
db_instance = fake_instance.fake_db_instance()
@ -2699,9 +2778,12 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase,
mock_volume.return_value = is_volume_backed
mock_get_inst.return_value = {'block_device_mapping': 'fake'}
self.compute.check_can_live_migrate_source(
result = self.compute.check_can_live_migrate_source(
self.context, instance=instance,
dest_check_data=dest_check_data)
self.assertEqual(can_numa_result, result)
mock_src_can_numa.assert_called_once_with(
self.context, dest_check_data, drvr_check_result)
mock_event.assert_called_once_with(
self.context, 'compute_check_can_live_migrate_source', CONF.host,
instance.uuid)
@ -2715,7 +2797,8 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase,
self.assertTrue(dest_check_data.is_volume_backed)
def _test_check_can_live_migrate_destination(self, do_raise=False):
def _test_check_can_live_migrate_destination(self, do_raise=False,
src_numa_lm=None):
db_instance = fake_instance.fake_db_instance(host='fake-host')
instance = objects.Instance._from_db_object(
self.context, objects.Instance(), db_instance)
@ -2724,11 +2807,14 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase,
disk_over_commit = 'disk_over_commit'
src_info = 'src_info'
dest_info = 'dest_info'
dest_check_data = dict(foo='bar')
mig_data = mock.MagicMock()
mig_data.cow = 'moo'
dest_check_data = objects.LibvirtLiveMigrateData(
dst_supports_numa_live_migration=True)
mig_data = objects.LibvirtLiveMigrateData()
if src_numa_lm is not None:
mig_data.src_supports_numa_live_migration = src_numa_lm
with test.nested(
mock.patch.object(self.compute, '_live_migration_claim'),
mock.patch.object(self.compute, '_get_compute_info'),
mock.patch.object(self.compute.driver,
'check_can_live_migrate_destination'),
@ -2739,16 +2825,24 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase,
mock.patch.object(db, 'instance_fault_create'),
mock.patch.object(compute_utils, 'EventReporter'),
mock.patch.object(migrate_data_obj.VIFMigrateData,
'create_skeleton_migrate_vifs'),
'create_skeleton_migrate_vifs',
return_value=[]),
mock.patch.object(instance, 'get_network_info'),
mock.patch.object(self.compute, '_claim_pci_for_instance_vifs'),
mock.patch.object(self.compute,
'_update_migrate_vifs_profile_with_pci'),
) as (mock_get, mock_check_dest, mock_check_src, mock_check_clean,
mock_fault_create, mock_event, mock_create_mig_vif,
mock_nw_info, mock_claim_pci, mock_update_mig_vif):
mock.patch.object(self.compute, '_dest_can_numa_live_migrate'),
mock.patch('nova.compute.manager.LOG'),
) as (mock_lm_claim, mock_get, mock_check_dest, mock_check_src,
mock_check_clean, mock_fault_create, mock_event,
mock_create_mig_vif, mock_nw_info, mock_claim_pci,
mock_update_mig_vif, mock_dest_can_numa, mock_log):
mock_get.side_effect = (src_info, dest_info)
mock_check_dest.return_value = dest_check_data
mock_dest_can_numa.return_value = dest_check_data
post_claim_md = objects.LibvirtLiveMigrateData(
dst_numa_info=objects.LibvirtLiveMigrateNUMAInfo())
mock_lm_claim.return_value = post_claim_md
if do_raise:
mock_check_src.side_effect = test.TestingException
@ -2757,16 +2851,33 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase,
else:
mock_check_src.return_value = mig_data
migration = objects.Migration()
limits = objects.SchedulerLimits()
result = self.compute.check_can_live_migrate_destination(
self.context, instance=instance,
block_migration=block_migration,
disk_over_commit=disk_over_commit)
disk_over_commit=disk_over_commit, migration=migration,
limits=limits)
if do_raise:
mock_fault_create.assert_called_once_with(self.context,
mock.ANY)
mock_check_src.assert_called_once_with(self.context, instance,
dest_check_data)
mock_dest_can_numa.assert_called_with(dest_check_data,
migration)
if not src_numa_lm:
mock_lm_claim.assert_not_called()
self.assertEqual(mig_data, result)
mock_log.info.assert_called()
self.assertThat(
mock_log.info.call_args[0][0],
testtools.matchers.MatchesRegex(
'Destination was ready for NUMA live migration'))
else:
mock_lm_claim.assert_called_once_with(
self.context, instance, mig_data, migration, limits)
self.assertEqual(post_claim_md, result)
mock_check_clean.assert_called_once_with(self.context,
dest_check_data)
mock_get.assert_has_calls([mock.call(self.context, 'fake-host'),
@ -2774,7 +2885,6 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase,
mock_check_dest.assert_called_once_with(self.context, instance,
src_info, dest_info, block_migration, disk_over_commit)
self.assertEqual(mig_data, result)
mock_event.assert_called_once_with(
self.context, 'compute_check_can_live_migrate_destination',
CONF.host, instance.uuid)
@ -2788,6 +2898,91 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase,
self._test_check_can_live_migrate_destination,
do_raise=True)
def test_check_can_live_migrate_destination_src_numa_lm_false(self):
self._test_check_can_live_migrate_destination(src_numa_lm=False)
def test_check_can_live_migrate_destination_src_numa_lm_true(self):
self._test_check_can_live_migrate_destination(src_numa_lm=True)
def test_dest_can_numa_live_migrate(self):
positive_dest_check_data = objects.LibvirtLiveMigrateData(
dst_supports_numa_live_migration=True)
negative_dest_check_data = objects.LibvirtLiveMigrateData()
self.assertNotIn(
'dst_supports_numa_live_migration',
self.compute._dest_can_numa_live_migrate(
copy.deepcopy(positive_dest_check_data), None))
self.assertIn(
'dst_supports_numa_live_migration',
self.compute._dest_can_numa_live_migrate(
copy.deepcopy(positive_dest_check_data), 'fake-migration'))
self.assertNotIn(
'dst_supports_numa_live_migration',
self.compute._dest_can_numa_live_migrate(
copy.deepcopy(negative_dest_check_data), 'fake-migration'))
self.assertNotIn(
'dst_supports_numa_live_migration',
self.compute._dest_can_numa_live_migrate(
copy.deepcopy(negative_dest_check_data), None))
def test_source_can_numa_live_migrate(self):
positive_dest_check_data = objects.LibvirtLiveMigrateData(
dst_supports_numa_live_migration=True)
negative_dest_check_data = objects.LibvirtLiveMigrateData()
positive_source_check_data = objects.LibvirtLiveMigrateData(
src_supports_numa_live_migration=True)
negative_source_check_data = objects.LibvirtLiveMigrateData()
with mock.patch.object(self.compute.compute_rpcapi,
'supports_numa_live_migration',
return_value=True
) as mock_supports_numa_lm:
self.assertIn(
'src_supports_numa_live_migration',
self.compute._source_can_numa_live_migrate(
self.context, positive_dest_check_data,
copy.deepcopy(positive_source_check_data)))
mock_supports_numa_lm.assert_called_once_with(self.context)
self.assertNotIn(
'src_supports_numa_live_migration',
self.compute._source_can_numa_live_migrate(
self.context, positive_dest_check_data,
copy.deepcopy(negative_source_check_data)))
self.assertNotIn(
'src_supports_numa_live_migration',
self.compute._source_can_numa_live_migrate(
self.context, negative_dest_check_data,
copy.deepcopy(positive_source_check_data)))
self.assertNotIn(
'src_supports_numa_live_migration',
self.compute._source_can_numa_live_migrate(
self.context, negative_dest_check_data,
copy.deepcopy(negative_source_check_data)))
with mock.patch.object(self.compute.compute_rpcapi,
'supports_numa_live_migration',
return_value=False
) as mock_supports_numa_lm:
self.assertNotIn(
'src_supports_numa_live_migration',
self.compute._source_can_numa_live_migrate(
self.context, positive_dest_check_data,
copy.deepcopy(positive_source_check_data)))
mock_supports_numa_lm.assert_called_once_with(self.context)
self.assertNotIn(
'src_supports_numa_live_migration',
self.compute._source_can_numa_live_migrate(
self.context, positive_dest_check_data,
copy.deepcopy(negative_source_check_data)))
self.assertNotIn(
'src_supports_numa_live_migration',
self.compute._source_can_numa_live_migrate(
self.context, negative_dest_check_data,
copy.deepcopy(positive_source_check_data)))
self.assertNotIn(
'src_supports_numa_live_migration',
self.compute._source_can_numa_live_migrate(
self.context, negative_dest_check_data,
copy.deepcopy(negative_source_check_data)))
@mock.patch('nova.compute.manager.InstanceEvents._lock_name')
def test_prepare_for_instance_event(self, lock_name_mock):
inst_obj = objects.Instance(uuid=uuids.instance)
@ -8395,6 +8590,8 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
_do_test()
def test_post_live_migration_at_destination_success(self):
@mock.patch.object(objects.Instance, 'drop_migration_context')
@mock.patch.object(objects.Instance, 'apply_migration_context')
@mock.patch.object(self.compute, 'rt')
@mock.patch.object(self.instance, 'save')
@mock.patch.object(self.compute.network_api, 'get_instance_nw_info',
@ -8413,7 +8610,7 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
_get_instance_block_device_info,
_notify_about_instance_usage, migrate_instance_finish,
setup_networks_on_host, get_instance_nw_info, save,
rt_mock):
rt_mock, mock_apply_mig_ctxt, mock_drop_mig_ctxt):
cn = mock.Mock(spec_set=['hypervisor_hostname'])
cn.hypervisor_hostname = 'test_host'
@ -8472,10 +8669,14 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
self.assertIsNone(self.instance.task_state)
save.assert_called_once_with(
expected_task_state=task_states.MIGRATING)
mock_apply_mig_ctxt.assert_called_once_with()
mock_drop_mig_ctxt.assert_called_once_with()
_do_test()
def test_post_live_migration_at_destination_compute_not_found(self):
@mock.patch.object(objects.Instance, 'drop_migration_context')
@mock.patch.object(objects.Instance, 'apply_migration_context')
@mock.patch.object(self.compute, 'rt')
@mock.patch.object(self.instance, 'save')
@mock.patch.object(self.compute, 'network_api')
@ -8492,7 +8693,7 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
_get_compute_info, _get_power_state,
_get_instance_block_device_info,
_notify_about_instance_usage, network_api,
save, rt_mock):
save, rt_mock, mock_apply_mig_ctxt, mock_drop_mig_ctxt):
cn = mock.Mock(spec_set=['hypervisor_hostname'])
cn.hypervisor_hostname = 'test_host'
_get_compute_info.return_value = cn
@ -8505,11 +8706,15 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
mock.call(self.context, self.instance, self.instance.host,
action='live_migration_post_dest', phase='end')])
self.assertIsNone(self.instance.node)
mock_apply_mig_ctxt.assert_called_with()
mock_drop_mig_ctxt.assert_called_once_with()
_do_test()
def test_post_live_migration_at_destination_unexpected_exception(self):
@mock.patch.object(objects.Instance, 'drop_migration_context')
@mock.patch.object(objects.Instance, 'apply_migration_context')
@mock.patch.object(self.compute, 'rt')
@mock.patch.object(compute_utils, 'add_instance_fault_from_exc')
@mock.patch.object(self.instance, 'save')
@ -8524,7 +8729,8 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
def _do_test(post_live_migration_at_destination, _get_compute_info,
_get_power_state, _get_instance_block_device_info,
_notify_about_instance_usage, network_api, save,
add_instance_fault_from_exc, rt_mock):
add_instance_fault_from_exc, rt_mock,
mock_apply_mig_ctxt, mock_drop_mig_ctxt):
cn = mock.Mock(spec_set=['hypervisor_hostname'])
cn.hypervisor_hostname = 'test_host'
_get_compute_info.return_value = cn
@ -8533,6 +8739,8 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
self.compute.post_live_migration_at_destination,
self.context, self.instance, False)
self.assertEqual(vm_states.ERROR, self.instance.vm_state)
mock_apply_mig_ctxt.assert_called_with()
mock_drop_mig_ctxt.assert_called_once_with()
_do_test()
@ -8543,6 +8751,8 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
"""Tests that neutron fails to delete the source host port bindings
but we handle the error and just log it.
"""
@mock.patch.object(self.instance, 'drop_migration_context')
@mock.patch.object(objects.Instance, 'apply_migration_context')
@mock.patch.object(self.compute, 'rt')
@mock.patch.object(self.compute, '_notify_about_instance_usage')
@mock.patch.object(self.compute, 'network_api')
@ -8554,7 +8764,8 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
hypervisor_hostname='fake-dest-host'))
@mock.patch.object(self.instance, 'save')
def _do_test(instance_save, get_compute_node, get_power_state,
get_bdms, network_api, legacy_notify, rt_mock):
get_bdms, network_api, legacy_notify, rt_mock,
mock_apply_mig_ctxt, mock_drop_mig_ctxt):
# setup_networks_on_host is called three times:
# 1. set the migrating_to port binding profile value (no-op)
# 2. delete the source host port bindings - we make this raise
@ -8569,6 +8780,8 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
self.assertEqual(1, mock_log_error.call_count)
self.assertIn('Network cleanup failed for source host',
mock_log_error.call_args[0][0])
mock_apply_mig_ctxt.assert_called_once_with()
mock_drop_mig_ctxt.assert_called_once_with()
_do_test()
@ -8579,13 +8792,14 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
@mock.patch.object(self.compute, 'compute_rpcapi')
@mock.patch.object(self.compute, '_notify_about_instance_usage')
@mock.patch.object(self.compute, 'network_api')
def _do_call(nwapi, notify, rpc, update):
@mock.patch.object(objects.Instance, 'refresh')
def _do_call(refresh, nwapi, notify, rpc, update):
bdms = objects.BlockDeviceMappingList(objects=[])
return self.compute._post_live_migration(self.context,
self.instance,
'foo',
*args, source_bdms=bdms,
**kwargs)
result = self.compute._post_live_migration(
self.context, self.instance, 'foo', *args, source_bdms=bdms,
**kwargs)
refresh.assert_called_once_with()
return result
mock_rt = self._mock_rt()
result = _do_call()
@ -8736,7 +8950,8 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
@mock.patch.object(self.compute, 'update_available_resource')
@mock.patch.object(self.compute, '_update_scheduler_instance_info')
@mock.patch.object(self.compute, '_clean_instance_console_tokens')
def _test(_clean_instance_console_tokens,
@mock.patch.object(objects.Instance, 'refresh')
def _test(_refresh, _clean_instance_console_tokens,
_update_scheduler_instance_info, update_available_resource,
driver_cleanup, _live_migration_cleanup_flags,
post_live_migration_at_destination,
@ -8750,6 +8965,7 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
post_live_migration_at_source.assert_called_once_with(
self.context, self.instance,
test.MatchType(network_model.NetworkInfo))
_refresh.assert_called_once_with()
driver_cleanup.assert_called_once_with(
self.context, self.instance,
test.MatchType(network_model.NetworkInfo), destroy_disks=False,
@ -8804,8 +9020,10 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
@mock.patch.object(compute, 'network_api')
@mock.patch.object(objects.BlockDeviceMappingList,
'get_by_instance_uuid')
def _test(mock_get_bdms, mock_net_api, mock_remove_conn, mock_usage,
mock_instance_save, mock_action, mock_attach_delete):
@mock.patch.object(objects.Instance, 'drop_migration_context')
def _test(mock_drop_mig_ctxt, mock_get_bdms, mock_net_api,
mock_remove_conn, mock_usage, mock_instance_save,
mock_action, mock_attach_delete):
# this tests that _rollback_live_migration replaces the bdm's
# attachment_id with the original attachment id that is in
# migrate_data.
@ -8822,6 +9040,7 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
self.assertEqual(bdm.attachment_id, orig_attachment_id)
self.assertEqual(orig_attachment_id, bdm.connection_info)
bdm.save.assert_called_once_with()
mock_drop_mig_ctxt.assert_called_once_with()
_test()
@ -8842,7 +9061,8 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
@mock.patch.object(self.instance, 'save')
@mock.patch.object(self.compute, 'network_api')
@mock.patch.object(self.compute, '_notify_about_instance_usage')
def _do_test(legacy_notify, network_api, instance_save,
@mock.patch.object(objects.Instance, 'drop_migration_context')
def _do_test(drop_mig_ctxt, legacy_notify, network_api, instance_save,
_revert_allocation):
# setup_networks_on_host is called two times:
# 1. set the migrating_to attribute in the port binding profile,
@ -8858,6 +9078,7 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
self.assertEqual(1, mock_log_error.call_count)
self.assertIn('Network cleanup failed for destination host',
mock_log_error.call_args[0][0])
drop_mig_ctxt.assert_called_once_with()
_do_test()
@ -8878,9 +9099,10 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
self.compute.network_api.setup_networks_on_host.side_effect = (
exception.PortBindingDeletionFailed(
port_id=uuids.port_id, host='fake-dest-host'))
mock_md = mock.MagicMock()
self.compute.rollback_live_migration_at_destination(
self.context, self.instance, destroy_disks=False,
migrate_data=mock.sentinel.migrate_data)
migrate_data=mock_md)
self.assertEqual(1, mock_log_error.call_count)
self.assertIn('Network cleanup failed for destination host',
mock_log_error.call_args[0][0])
@ -8888,7 +9110,7 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
self.context, self.instance,
network_api.get_instance_nw_info.return_value,
get_bdms.return_value, destroy_disks=False,
migrate_data=mock.sentinel.migrate_data)
migrate_data=mock_md)
rt_mock.free_pci_device_claims_for_instance.\
assert_called_once_with(self.context, self.instance)

View File

@ -2846,8 +2846,9 @@ class TestLiveMigration(BaseTestCase):
mock.patch.object(objects.Instance, 'save'),
mock.patch.object(self.rt, '_update'),
mock.patch.object(self.rt.pci_tracker, 'claim_instance'),
mock.patch.object(self.rt, '_update_usage_from_migration')
) as (mock_from_instance, mock_migration_save, mock_instance_save,
mock_update, mock_pci_claim_instance):
mock_update, mock_pci_claim_instance, mock_update_usage):
claim = self.rt.live_migration_claim(ctxt, instance, _NODENAME,
migration, limits=None)
self.assertEqual(42, claim.migration.id)
@ -2859,23 +2860,11 @@ class TestLiveMigration(BaseTestCase):
mock_update.assert_called_with(
mock.ANY, _COMPUTE_NODE_FIXTURES[0])
mock_pci_claim_instance.assert_not_called()
mock_update_usage.assert_called_with(ctxt, instance, migration,
_NODENAME)
class TestUpdateUsageFromMigration(test.NoDBTestCase):
@mock.patch('nova.compute.resource_tracker.ResourceTracker.'
'_get_instance_type')
def test_unsupported_move_type(self, get_mock):
rt = resource_tracker.ResourceTracker(_HOSTNAME,
mock.sentinel.virt_driver)
migration = objects.Migration(migration_type='live-migration')
# For same-node migrations, the RT's _get_instance_type() method is
# called if there is a migration that is trackable. Here, we want to
# ensure that this method isn't called for live-migration migrations.
rt._update_usage_from_migration(mock.sentinel.ctx,
mock.sentinel.instance,
migration,
_NODENAME)
self.assertFalse(get_mock.called)
def test_missing_old_flavor_outbound_resize(self):
"""Tests the case that an instance is not being tracked on the source
@ -3039,6 +3028,25 @@ class TestUpdateUsageFromMigrations(BaseTestCase):
mock.sentinel.ctx, instance, migration, _NODENAME)
mock_update_usage.reset_mock()
@mock.patch('nova.objects.migration.Migration.save')
@mock.patch.object(resource_tracker.ResourceTracker,
'_update_usage_from_migration')
def test_live_migrating_state(self, mock_update_usage, mock_save):
self._setup_rt()
migration_context = objects.MigrationContext(migration_id=1)
instance = objects.Instance(
vm_state=vm_states.ACTIVE, task_state=task_states.MIGRATING,
migration_context=migration_context)
migration = objects.Migration(
source_compute='other-host', source_node='other-node',
dest_compute=_HOSTNAME, dest_node=_NODENAME,
instance_uuid=uuids.instance, id=1, instance=instance,
migration_type='live-migration')
self.rt._update_usage_from_migrations(
mock.sentinel.ctx, [migration], _NODENAME)
mock_update_usage.assert_called_once_with(
mock.sentinel.ctx, instance, migration, _NODENAME)
class TestUpdateUsageFromInstance(BaseTestCase):
@ -3461,6 +3469,38 @@ class TestInstanceInResizeState(test.NoDBTestCase):
self.assertTrue(resource_tracker._instance_in_resize_state(instance))
class TestInstanceIsLiveMigrating(test.NoDBTestCase):
def test_migrating_active(self):
instance = objects.Instance(vm_state=vm_states.ACTIVE,
task_state=task_states.MIGRATING)
self.assertTrue(
resource_tracker._instance_is_live_migrating(instance))
def test_migrating_paused(self):
instance = objects.Instance(vm_state=vm_states.PAUSED,
task_state=task_states.MIGRATING)
self.assertTrue(
resource_tracker._instance_is_live_migrating(instance))
def test_migrating_other(self):
instance = objects.Instance(vm_state=vm_states.STOPPED,
task_state=task_states.MIGRATING)
self.assertFalse(
resource_tracker._instance_is_live_migrating(instance))
def test_non_migrating_active(self):
instance = objects.Instance(vm_state=vm_states.ACTIVE,
task_state=None)
self.assertFalse(
resource_tracker._instance_is_live_migrating(instance))
def test_non_migrating_paused(self):
instance = objects.Instance(vm_state=vm_states.PAUSED,
task_state=None)
self.assertFalse(
resource_tracker._instance_is_live_migrating(instance))
class TestSetInstanceHostAndNode(BaseTestCase):
def setUp(self):
@ -3579,22 +3619,6 @@ class ComputeMonitorTestCase(BaseTestCase):
self.assertEqual(metrics, expected_metrics)
class TestIsTrackableMigration(test.NoDBTestCase):
def test_true(self):
mig = objects.Migration()
for mig_type in ('resize', 'migration', 'evacuation'):
mig.migration_type = mig_type
self.assertTrue(resource_tracker._is_trackable_migration(mig))
def test_false(self):
mig = objects.Migration()
for mig_type in ('live-migration',):
mig.migration_type = mig_type
self.assertFalse(resource_tracker._is_trackable_migration(mig))
class OverCommitTestCase(BaseTestCase):
def test_cpu_allocation_ratio_none_negative(self):
self.assertRaises(ValueError,

View File

@ -425,6 +425,20 @@ class ComputeRpcAPITestCase(test.NoDBTestCase):
migrate_data=None, version='5.0',
call_monitor_timeout=60, timeout=1234)
def test_supports_numa_live_migration(self):
mock_client = mock.MagicMock()
rpcapi = compute_rpcapi.ComputeAPI()
rpcapi.router.client = mock.Mock()
rpcapi.router.client.return_value = mock_client
ctxt = context.RequestContext('fake_user', 'fake_project'),
mock_client.can_send_version.return_value = False
self.assertFalse(rpcapi.supports_numa_live_migration(ctxt))
mock_client.can_send_version.return_value = True
self.assertTrue(rpcapi.supports_numa_live_migration(ctxt))
mock_client.can_send_version.assert_has_calls(
[mock.call('5.3'), mock.call('5.3')])
def test_check_can_live_migrate_destination(self):
self.flags(long_rpc_timeout=1234)
self._test_compute_api('check_can_live_migrate_destination', 'call',
@ -432,9 +446,43 @@ class ComputeRpcAPITestCase(test.NoDBTestCase):
destination='dest',
block_migration=False,
disk_over_commit=False,
version='5.0', call_monitor_timeout=60,
version='5.3', call_monitor_timeout=60,
migration='migration',
limits='limits',
timeout=1234)
def test_check_can_live_migrate_destination_backlevel(self):
mock_cctxt = mock.MagicMock()
mock_client = mock.MagicMock()
mock_client.can_send_version.return_value = False
mock_client.prepare.return_value = mock_cctxt
rpcapi = compute_rpcapi.ComputeAPI()
rpcapi.router.client = mock.Mock()
rpcapi.router.client.return_value = mock_client
ctxt = context.RequestContext('fake_user', 'fake_project'),
rpcapi.check_can_live_migrate_destination(
ctxt, instance=self.fake_instance_obj,
destination='dest',
block_migration=False,
disk_over_commit=False,
migration='migration',
limits='limits')
mock_client.prepare.assert_called_with(server='dest', version='5.0',
call_monitor_timeout=mock.ANY,
timeout=mock.ANY)
mock_cctxt.call.assert_called_once_with(
ctxt, 'check_can_live_migrate_destination',
instance=self.fake_instance_obj, block_migration=False,
disk_over_commit=False)
def test_drop_move_claim_at_destination(self):
self._test_compute_api('drop_move_claim_at_destination', 'call',
instance=self.fake_instance_obj, host='host',
version='5.3', _return_value=None)
def test_prep_resize(self):
self._test_compute_api('prep_resize', 'cast',
instance=self.fake_instance_obj,

View File

@ -31,10 +31,12 @@ from nova import test
from nova.tests.unit import fake_instance
fake_limits1 = objects.SchedulerLimits()
fake_selection1 = objects.Selection(service_host="host1", nodename="node1",
cell_uuid=uuids.cell)
cell_uuid=uuids.cell, limits=fake_limits1)
fake_limits2 = objects.SchedulerLimits()
fake_selection2 = objects.Selection(service_host="host2", nodename="node2",
cell_uuid=uuids.cell)
cell_uuid=uuids.cell, limits=fake_limits2)
class LiveMigrationTaskTestCase(test.NoDBTestCase):
@ -150,7 +152,7 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase):
mock.patch('nova.conductor.tasks.migrate.'
'replace_allocation_with_migration'),
) as (mock_check, mock_find, mock_mig, mock_save, mock_alloc):
mock_find.return_value = ("found_host", "found_node")
mock_find.return_value = ("found_host", "found_node", None)
mock_mig.return_value = "bob"
mock_alloc.return_value = (mock.MagicMock(), mock.MagicMock())
@ -264,6 +266,7 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase):
ram_allocation_ratio=1.0)
mock_get_info.return_value = hypervisor_details
mock_check.return_value = "migrate_data"
self.task.limits = fake_limits1
with test.nested(
mock.patch.object(self.task.network_api,
@ -280,7 +283,8 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase):
mock.call(self.destination)],
mock_get_info.call_args_list)
mock_check.assert_called_once_with(self.context, self.instance,
self.destination, self.block_migration, self.disk_over_commit)
self.destination, self.block_migration, self.disk_over_commit,
self.task.migration, fake_limits1)
def test_check_requested_destination_fails_with_same_dest(self):
self.task.destination = "same"
@ -392,7 +396,8 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase):
@mock.patch.object(scheduler_utils, 'setup_instance_group')
def test_find_destination_works(self, mock_setup, mock_reset, mock_select,
mock_check, mock_call):
self.assertEqual(("host1", "node1"), self.task._find_destination())
self.assertEqual(("host1", "node1", fake_limits1),
self.task._find_destination())
# Make sure the request_spec was updated to include the cell
# mapping.
@ -422,7 +427,8 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase):
mock_check, mock_call):
self.instance['image_ref'] = ''
self.assertEqual(("host1", "node1"), self.task._find_destination())
self.assertEqual(("host1", "node1", fake_limits1),
self.task._find_destination())
mock_setup.assert_called_once_with(self.context, self.fake_spec)
mock_select.assert_called_once_with(
@ -445,7 +451,8 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase):
mock_remove):
mock_check.side_effect = [error, None]
self.assertEqual(("host2", "node2"), self.task._find_destination())
self.assertEqual(("host2", "node2", fake_limits2),
self.task._find_destination())
# Should have removed allocations for the first host.
mock_remove.assert_called_once_with('host1', 'node1')
mock_setup.assert_called_once_with(self.context, self.fake_spec)
@ -479,7 +486,8 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase):
self.flags(migrate_max_retries=1)
mock_call.side_effect = [exception.Invalid(), None]
self.assertEqual(("host2", "node2"), self.task._find_destination())
self.assertEqual(("host2", "node2", fake_limits2),
self.task._find_destination())
# Should have removed allocations for the first host.
mock_remove.assert_called_once_with('host1', 'node1')
mock_setup.assert_called_once_with(self.context, self.fake_spec)
@ -506,7 +514,8 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase):
mock_call.side_effect = [exception.MigrationPreCheckError('reason'),
None]
self.assertEqual(("host2", "node2"), self.task._find_destination())
self.assertEqual(("host2", "node2", fake_limits2),
self.task._find_destination())
# Should have removed allocations for the first host.
mock_remove.assert_called_once_with('host1', 'node1')
mock_setup.assert_called_once_with(self.context, self.fake_spec)

View File

@ -9879,6 +9879,34 @@ class LibvirtConnTestCase(test.NoDBTestCase,
'serial_listen_addr': None},
result.obj_to_primitive()['nova_object.data'])
@mock.patch.object(libvirt_driver.LibvirtDriver,
'_create_shared_storage_test_file',
return_value='fake')
@mock.patch.object(libvirt_driver.LibvirtDriver, '_compare_cpu')
def test_check_can_live_migrate_dest_numa_lm(
self, mock_cpu, mock_test_file):
instance_ref = objects.Instance(**self.test_instance)
instance_ref.numa_topology = objects.InstanceNUMATopology(
cells=[objects.InstanceNUMACell()])
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
compute_info = {'cpu_info': 'asdf', 'disk_available_least': 1}
result = drvr.check_can_live_migrate_destination(
self.context, instance_ref, compute_info, compute_info)
self.assertTrue(result.dst_supports_numa_live_migration)
@mock.patch.object(libvirt_driver.LibvirtDriver,
'_create_shared_storage_test_file',
return_value='fake')
@mock.patch.object(libvirt_driver.LibvirtDriver, '_compare_cpu')
def test_check_can_live_migrate_dest_numa_lm_no_instance_numa(
self, mock_cpu, mock_test_file):
instance_ref = objects.Instance(**self.test_instance)
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
compute_info = {'cpu_info': 'asdf', 'disk_available_least': 1}
result = drvr.check_can_live_migrate_destination(
self.context, instance_ref, compute_info, compute_info)
self.assertNotIn('dst_supports_numa_live_migration', result)
@mock.patch.object(libvirt_driver.LibvirtDriver,
'_create_shared_storage_test_file')
@mock.patch.object(fakelibvirt.Connection, 'compareCPU')
@ -10153,8 +10181,12 @@ class LibvirtConnTestCase(test.NoDBTestCase,
is_shared_block_storage=False,
is_shared_instance_path=False,
disk_available_mb=1024,
exception=None):
exception=None,
numa_lm=True):
instance = objects.Instance(**self.test_instance)
if numa_lm:
instance.numa_topology = objects.InstanceNUMATopology(cells=[
objects.InstanceNUMACell()])
dest_check_data = objects.LibvirtLiveMigrateData(
filename='file',
image_type='default',
@ -10172,6 +10204,10 @@ class LibvirtConnTestCase(test.NoDBTestCase,
else:
ret = drvr.check_can_live_migrate_source(self.context, instance,
dest_check_data)
if numa_lm:
self.assertTrue(ret.src_supports_numa_live_migration)
else:
self.assertNotIn('src_supports_numa_live_migration', ret)
mock_is_shared.assert_called_once_with(instance, dest_check_data, None)
mock_check_shared.assert_called_once_with('file', instance)
@ -10204,6 +10240,12 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.context, instance, dest_check_data.disk_available_mb,
False, None)
def test_check_can_live_migrate_source_numa_lm(self):
self._test_can_live_migrate_source(is_shared_block_storage=True,
numa_lm=True)
self._test_can_live_migrate_source(is_shared_block_storage=True,
numa_lm=False)
def test_check_can_live_migrate_source_shared_block_storage(self):
self._test_can_live_migrate_source(is_shared_block_storage=True)

View File

@ -7531,6 +7531,13 @@ class LibvirtDriver(driver.ComputeDriver):
self._host.has_min_version(MIN_LIBVIRT_FILE_BACKED_DISCARD_VERSION,
MIN_QEMU_FILE_BACKED_DISCARD_VERSION))
# TODO(artom) Set to indicate that the destination (us) can perform a
# NUMA-aware live migration. NUMA-aware live migration will become
# unconditionally supported in RPC 6.0, so this sentinel can be removed
# then.
if instance.numa_topology:
data.dst_supports_numa_live_migration = True
return data
def post_claim_migrate_data(self, context, instance, migrate_data, claim):
@ -7665,6 +7672,13 @@ class LibvirtDriver(driver.ComputeDriver):
# default to True here for all computes >= Queens.
dest_check_data.src_supports_native_luks = True
# TODO(artom) Set to indicate that the source (us) can perform a
# NUMA-aware live migration. NUMA-aware live migration will become
# unconditionally supported in RPC 6.0, so this sentinel can be removed
# then.
if instance.numa_topology:
dest_check_data.src_supports_numa_live_migration = True
return dest_check_data
def _is_shared_block_storage(self, instance, dest_check_data,