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:
parent
202aae6652
commit
b335d0c157
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'},
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue