summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2012-07-16 20:38:14 +0000
committerGerrit Code Review <review@openstack.org>2012-07-16 20:38:14 +0000
commit500ee77121512fd40f5c2afb885fdc45fbf4b57f (patch)
treebe5e90fa6f9206c337fa13c837f7f8eecadfcbb4
parentc04012242185cc18de13452c7442c7e800552a01 (diff)
parentcac332c39645286a11c009094a86f62d02752183 (diff)
Merge "Refactoring required for blueprint xenapi-live-migration"
-rw-r--r--nova/compute/manager.py141
-rw-r--r--nova/compute/rpcapi.py37
-rw-r--r--nova/scheduler/driver.py206
-rw-r--r--nova/tests/compute/test_compute.py209
-rw-r--r--nova/tests/compute/test_rpcapi.py30
-rw-r--r--nova/tests/scheduler/test_scheduler.py341
-rw-r--r--nova/tests/test_libvirt.py178
-rw-r--r--nova/tests/test_virt_drivers.py26
-rw-r--r--nova/virt/baremetal/driver.py3
-rw-r--r--nova/virt/driver.py68
-rw-r--r--nova/virt/fake.py7
-rw-r--r--nova/virt/libvirt/driver.py169
-rw-r--r--nova/virt/xenapi/driver.py4
13 files changed, 757 insertions, 662 deletions
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index d0818c287..061d596b9 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -68,6 +68,7 @@ from nova.openstack.common import jsonutils
from nova.openstack.common import log as logging
from nova.openstack.common.notifier import api as notifier
from nova.openstack.common import rpc
+from nova.openstack.common.rpc import common as rpc_common
from nova.openstack.common import timeutils
from nova import utils
from nova.virt import driver
@@ -233,7 +234,7 @@ def _get_additional_capabilities():
class ComputeManager(manager.SchedulerDependentManager):
"""Manages the running instances from creation to destruction."""
- RPC_API_VERSION = '1.1'
+ RPC_API_VERSION = '1.2'
def __init__(self, compute_driver=None, *args, **kwargs):
"""Load configuration options and connect to the hypervisor."""
@@ -1891,74 +1892,72 @@ class ComputeManager(manager.SchedulerDependentManager):
except exception.NotFound:
pass
- @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
- def compare_cpu(self, context, cpu_info):
- """Checks that the host cpu is compatible with a cpu given by xml.
+ def get_instance_disk_info(self, context, instance_name):
+ """Getting infomation of instance's current disk.
+
+ Implementation nova.virt.libvirt.connection.
:param context: security context
- :param cpu_info: json string obtained from virConnect.getCapabilities
- :returns: See driver.compare_cpu
+ :param instance_name: instance name
"""
- return self.driver.compare_cpu(cpu_info)
+ return self.driver.get_instance_disk_info(instance_name)
@exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
- def create_shared_storage_test_file(self, context):
- """Makes tmpfile under FLAGS.instance_path.
-
- This method creates a temporary file that acts as an indicator to
- other compute nodes that utilize the same shared storage as this node.
- (create|check|cleanup)_shared_storage_test_file() are a set and should
- be run together.
-
- :param context: security context
- :returns: tmpfile name(basename)
+ def compare_cpu(self, context, cpu_info):
+ raise rpc_common.RPCException(message=_('Deprecated from version 1.2'))
- """
- dirpath = FLAGS.instances_path
- fd, tmp_file = tempfile.mkstemp(dir=dirpath)
- LOG.debug(_("Creating tmpfile %s to notify to other "
- "compute nodes that they should mount "
- "the same storage.") % tmp_file)
- os.close(fd)
- return os.path.basename(tmp_file)
+ @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
+ def create_shared_storage_test_file(self, context):
+ raise rpc_common.RPCException(message=_('Deprecated from version 1.2'))
@exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
def check_shared_storage_test_file(self, context, filename):
- """Confirms existence of the tmpfile under FLAGS.instances_path.
- Cannot confirm tmpfile return False.
-
- :param context: security context
- :param filename: confirm existence of FLAGS.instances_path/thisfile
-
- """
- tmp_file = os.path.join(FLAGS.instances_path, filename)
- if not os.path.exists(tmp_file):
- return False
- else:
- return True
+ raise rpc_common.RPCException(message=_('Deprecated from version 1.2'))
@exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
def cleanup_shared_storage_test_file(self, context, filename):
- """Removes existence of the tmpfile under FLAGS.instances_path.
+ raise rpc_common.RPCException(message=_('Deprecated from version 1.2'))
- :param context: security context
- :param filename: remove existence of FLAGS.instances_path/thisfile
+ @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
+ def check_can_live_migrate_destination(self, ctxt, instance_id,
+ block_migration=False,
+ disk_over_commit=False):
+ """Check if it is possible to execute live migration.
+ This runs checks on the destination host, and then calls
+ back to the source host to check the results.
+
+ :param context: security context
+ :param instance_id: nova.db.sqlalchemy.models.Instance.Id
+ :param block_migration: if true, prepare for block migration
+ :param disk_over_commit: if true, allow disk over commit
"""
- tmp_file = os.path.join(FLAGS.instances_path, filename)
- os.remove(tmp_file)
+ instance_ref = self.db.instance_get(ctxt, instance_id)
+ dest_check_data = self.driver.check_can_live_migrate_destination(ctxt,
+ instance_ref, block_migration, disk_over_commit)
+ try:
+ self.compute_rpcapi.check_can_live_migrate_source(ctxt,
+ instance_ref, dest_check_data)
+ finally:
+ self.driver.check_can_live_migrate_destination_cleanup(ctxt,
+ dest_check_data)
- def get_instance_disk_info(self, context, instance_name):
- """Get information about instance's current disk.
+ @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
+ def check_can_live_migrate_source(self, ctxt, instance_id,
+ dest_check_data):
+ """Check if it is possible to execute live migration.
- Implementation nova.virt.libvirt.connection.
+ This checks if the live migration can succeed, based on the
+ results from check_can_live_migrate_destination.
:param context: security context
- :param instance_name: instance name
-
+ :param instance_id: nova.db.sqlalchemy.models.Instance.Id
+ :param dest_check_data: result of check_can_live_migrate_destination
"""
- return self.driver.get_instance_disk_info(instance_name)
+ instance_ref = self.db.instance_get(ctxt, instance_id)
+ self.driver.check_can_live_migrate_source(ctxt, instance_ref,
+ dest_check_data)
def pre_live_migration(self, context, instance_id,
block_migration=False, disk=None):
@@ -1978,40 +1977,21 @@ class ComputeManager(manager.SchedulerDependentManager):
if not block_device_info['block_device_mapping']:
LOG.info(_('Instance has no volume.'), instance=instance_ref)
- self.driver.pre_live_migration(block_device_info)
-
- # NOTE(tr3buchet): setup networks on destination host
- self.network_api.setup_networks_on_host(context, instance_ref,
- self.host)
-
- # Bridge settings.
- # Call this method prior to ensure_filtering_rules_for_instance,
- # since bridge is not set up, ensure_filtering_rules_for instance
- # fails.
- #
- # Retry operation is necessary because continuously request comes,
- # concorrent request occurs to iptables, then it complains.
network_info = self._get_instance_nw_info(context, instance_ref)
# TODO(tr3buchet): figure out how on the earth this is necessary
fixed_ips = network_info.fixed_ips()
if not fixed_ips:
- raise exception.FixedIpNotFoundForInstance(instance_id=instance_id)
+ raise exception.FixedIpNotFoundForInstance(
+ instance_id=instance_id)
- max_retry = FLAGS.live_migration_retry_count
- for cnt in range(max_retry):
- try:
- self.driver.plug_vifs(instance_ref,
- self._legacy_nw_info(network_info))
- break
- except exception.ProcessExecutionError:
- if cnt == max_retry - 1:
- raise
- else:
- LOG.warn(_("plug_vifs() failed %(cnt)d."
- "Retry up to %(max_retry)d for %(hostname)s.")
- % locals(), instance=instance_ref)
- time.sleep(1)
+ self.driver.pre_live_migration(context, instance_ref,
+ block_device_info,
+ self._legacy_nw_info(network_info))
+
+ # NOTE(tr3buchet): setup networks on destination host
+ self.network_api.setup_networks_on_host(context, instance_ref,
+ self.host)
# Creating filters to hypervisors and firewalls.
# An example is that nova-instance-instance-xxx,
@@ -2082,12 +2062,11 @@ class ComputeManager(manager.SchedulerDependentManager):
and mainly updating database record.
:param ctxt: security context
- :param instance_id: nova.db.sqlalchemy.models.Instance.Id
+ :param instance_ref: nova.db.sqlalchemy.models.Instance
:param dest: destination host
:param block_migration: if true, prepare for block migration
"""
-
LOG.info(_('post_live_migration() is started..'),
instance=instance_ref)
@@ -2160,8 +2139,8 @@ class ComputeManager(manager.SchedulerDependentManager):
"This error can be safely ignored."),
instance=instance_ref)
- def post_live_migration_at_destination(self, context,
- instance_id, block_migration=False):
+ def post_live_migration_at_destination(self, context, instance_id,
+ block_migration=False):
"""Post operations for live migration .
:param context: security context
@@ -2203,7 +2182,7 @@ class ComputeManager(manager.SchedulerDependentManager):
"""Recovers Instance/volume state from migrating -> running.
:param context: security context
- :param instance_id: nova.db.sqlalchemy.models.Instance.Id
+ :param instance_ref: nova.db.sqlalchemy.models.Instance
:param dest:
This method is called from live migration src host.
This param specifies destination host.
diff --git a/nova/compute/rpcapi.py b/nova/compute/rpcapi.py
index d740925a7..332b8153e 100644
--- a/nova/compute/rpcapi.py
+++ b/nova/compute/rpcapi.py
@@ -21,6 +21,7 @@ Client side of the compute RPC API.
from nova import exception
from nova import flags
from nova.openstack.common import rpc
+from nova.openstack.common.rpc import common as rpc_common
import nova.openstack.common.rpc.proxy
@@ -55,6 +56,7 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy):
1.0 - Initial version.
1.1 - Adds get_host_uptime()
+ 1.2 - Adds check_can_live_migrate_[destination|source]
'''
BASE_RPC_API_VERSION = '1.0'
@@ -88,19 +90,33 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy):
mountpoint=mountpoint),
topic=_compute_topic(self.topic, ctxt, None, instance))
+ def check_can_live_migrate_destination(self, ctxt, instance, destination,
+ block_migration, disk_over_commit):
+ self.call(ctxt, self.make_msg('check_can_live_migrate_destination',
+ instance_id=instance['id'],
+ block_migration=block_migration,
+ disk_over_commit=disk_over_commit),
+ topic=_compute_topic(self.topic, ctxt, destination, None),
+ version='1.2')
+
+ def check_can_live_migrate_source(self, ctxt, instance, dest_check_data):
+ self.call(ctxt, self.make_msg('check_can_live_migrate_source',
+ instance_id=instance['id'],
+ dest_check_data=dest_check_data),
+ topic=_compute_topic(self.topic, ctxt, None, instance),
+ version='1.2')
+
def check_shared_storage_test_file(self, ctxt, filename, host):
- return self.call(ctxt, self.make_msg('check_shared_storage_test_file',
- filename=filename),
- topic=_compute_topic(self.topic, ctxt, host, None))
+ raise rpc_common.RPCException(message=_('Deprecated from version 1.2'))
def cleanup_shared_storage_test_file(self, ctxt, filename, host):
- self.cast(ctxt, self.make_msg('cleanup_shared_storage_test_file',
- filename=filename),
- topic=_compute_topic(self.topic, ctxt, host, None))
+ raise rpc_common.RPCException(message=_('Deprecated from version 1.2'))
def compare_cpu(self, ctxt, cpu_info, host):
- return self.call(ctxt, self.make_msg('compare_cpu', cpu_info=cpu_info),
- topic=_compute_topic(self.topic, ctxt, host, None))
+ raise rpc_common.RPCException(message=_('Deprecated from version 1.2'))
+
+ def create_shared_storage_test_file(self, ctxt, host):
+ raise rpc_common.RPCException(message=_('Deprecated from version 1.2'))
def confirm_resize(self, ctxt, instance, migration_id, host,
cast=True):
@@ -109,11 +125,6 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy):
instance_uuid=instance['uuid'], migration_id=migration_id),
topic=_compute_topic(self.topic, ctxt, host, instance))
- def create_shared_storage_test_file(self, ctxt, host):
- return self.call(ctxt,
- self.make_msg('create_shared_storage_test_file'),
- topic=_compute_topic(self.topic, ctxt, host, None))
-
def detach_volume(self, ctxt, instance, volume_id):
self.cast(ctxt, self.make_msg('detach_volume',
instance_uuid=instance['uuid'], volume_id=volume_id),
diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py
index be39d5a0a..db6acb88f 100644
--- a/nova/scheduler/driver.py
+++ b/nova/scheduler/driver.py
@@ -212,36 +212,30 @@ class Scheduler(object):
The host where instance is running currently.
Then scheduler send request that host.
"""
- # Whether instance exists and is running.
+ # Check we can do live migration
instance_ref = db.instance_get(context, instance_id)
-
- # Checking instance.
self._live_migration_src_check(context, instance_ref)
+ self._live_migration_dest_check(context, instance_ref, dest)
+ self._live_migration_common_check(context, instance_ref, dest)
+ self.compute_rpcapi.check_can_live_migrate_destination(context,
+ instance_ref, dest, block_migration, disk_over_commit)
- # Checking destination host.
- self._live_migration_dest_check(context, instance_ref,
- dest, block_migration,
- disk_over_commit)
- # Common checking.
- self._live_migration_common_check(context, instance_ref,
- dest, block_migration,
- disk_over_commit)
-
- # Changing instance_state.
+ # Change instance_state
values = {"task_state": task_states.MIGRATING}
# update instance state and notify
(old_ref, new_instance_ref) = db.instance_update_and_get_original(
- context, instance_id, values)
+ context, instance_ref['uuid'], values)
notifications.send_update(context, old_ref, new_instance_ref,
service="scheduler")
+ # Perform migration
src = instance_ref['host']
cast_to_compute_host(context, src, 'live_migration',
- update_db=False,
- instance_id=instance_id,
- dest=dest,
- block_migration=block_migration)
+ update_db=False,
+ instance_id=instance_id,
+ dest=dest,
+ block_migration=block_migration)
def _live_migration_src_check(self, context, instance_ref):
"""Live migration check routine (for src host).
@@ -250,7 +244,7 @@ class Scheduler(object):
:param instance_ref: nova.db.sqlalchemy.models.Instance object
"""
-
+ # TODO(johngar) why is this not in the API layer?
# Checking instance is running.
if instance_ref['power_state'] != power_state.RUNNING:
raise exception.InstanceNotRunning(
@@ -258,22 +252,21 @@ class Scheduler(object):
# Checking src host exists and compute node
src = instance_ref['host']
- services = db.service_get_all_compute_by_host(context, src)
+ try:
+ services = db.service_get_all_compute_by_host(context, src)
+ except exception.NotFound:
+ raise exception.ComputeServiceUnavailable(host=src)
# Checking src host is alive.
if not utils.service_is_up(services[0]):
raise exception.ComputeServiceUnavailable(host=src)
- def _live_migration_dest_check(self, context, instance_ref, dest,
- block_migration, disk_over_commit):
+ def _live_migration_dest_check(self, context, instance_ref, dest):
"""Live migration check routine (for destination host).
:param context: security context
:param instance_ref: nova.db.sqlalchemy.models.Instance object
:param dest: destination host
- :param block_migration: if true, block_migration.
- :param disk_over_commit: if True, consider real(not virtual)
- disk size.
"""
# Checking dest exists and compute node.
@@ -291,15 +284,11 @@ class Scheduler(object):
raise exception.UnableToMigrateToSelf(
instance_id=instance_ref['uuid'], host=dest)
- # Checking dst host still has enough capacities.
- self.assert_compute_node_has_enough_resources(context,
- instance_ref,
- dest,
- block_migration,
- disk_over_commit)
+ # Check memory requirements
+ self._assert_compute_node_has_enough_memory(context,
+ instance_ref, dest)
- def _live_migration_common_check(self, context, instance_ref, dest,
- block_migration, disk_over_commit):
+ def _live_migration_common_check(self, context, instance_ref, dest):
"""Live migration common check routine.
Below checkings are followed by
@@ -308,38 +297,10 @@ class Scheduler(object):
:param context: security context
:param instance_ref: nova.db.sqlalchemy.models.Instance object
:param dest: destination host
- :param block_migration: if true, block_migration.
- :param disk_over_commit: if True, consider real(not virtual)
- disk size.
-
"""
-
- # Checking shared storage connectivity
- # if block migration, instances_paths should not be on shared storage.
- shared = self.mounted_on_same_shared_storage(context, instance_ref,
- dest)
- if block_migration:
- if shared:
- reason = _("Block migration can not be used "
- "with shared storage.")
- raise exception.InvalidSharedStorage(reason=reason, path=dest)
-
- elif not shared:
- reason = _("Live migration can not be used "
- "without shared storage.")
- raise exception.InvalidSharedStorage(reason=reason, path=dest)
-
- # Checking destination host exists.
- dservice_refs = db.service_get_all_compute_by_host(context, dest)
- dservice_ref = dservice_refs[0]['compute_node'][0]
-
- # Checking original host( where instance was launched at) exists.
- try:
- oservice_refs = db.service_get_all_compute_by_host(context,
- instance_ref['host'])
- except exception.NotFound:
- raise exception.SourceHostUnavailable()
- oservice_ref = oservice_refs[0]['compute_node'][0]
+ dservice_ref = self._get_compute_info(context, dest)
+ src = instance_ref['host']
+ oservice_ref = self._get_compute_info(context, src)
# Checking hypervisor is same.
orig_hypervisor = oservice_ref['hypervisor_type']
@@ -353,40 +314,7 @@ class Scheduler(object):
if orig_hypervisor > dest_hypervisor:
raise exception.DestinationHypervisorTooOld()
- # Checking cpuinfo.
- try:
- self.compute_rpcapi.compare_cpu(context, oservice_ref['cpu_info'],
- dest)
-
- except exception.InvalidCPUInfo:
- src = instance_ref['host']
- LOG.exception(_("host %(dest)s is not compatible with "
- "original host %(src)s.") % locals())
- raise
-
- def assert_compute_node_has_enough_resources(self, context, instance_ref,
- dest, block_migration,
- disk_over_commit):
-
- """Checks if destination host has enough resource for live migration.
-
- :param context: security context
- :param instance_ref: nova.db.sqlalchemy.models.Instance object
- :param dest: destination host
- :param block_migration: if true, block_migration.
- :param disk_over_commit: if True, consider real(not virtual)
- disk size.
-
- """
- self.assert_compute_node_has_enough_memory(context,
- instance_ref, dest)
- if not block_migration:
- return
- self.assert_compute_node_has_enough_disk(context,
- instance_ref, dest,
- disk_over_commit)
-
- def assert_compute_node_has_enough_memory(self, context,
+ def _assert_compute_node_has_enough_memory(self, context,
instance_ref, dest):
"""Checks if destination host has enough memory for live migration.
@@ -397,7 +325,7 @@ class Scheduler(object):
"""
# Getting total available memory of host
- avail = self._get_compute_info(context, dest, 'memory_mb')
+ avail = self._get_compute_info(context, dest)['memory_mb']
# Getting total used memory and disk of host
# It should be sum of memories that are assigned as max value,
@@ -414,54 +342,7 @@ class Scheduler(object):
"instance:%(mem_inst)s)")
raise exception.MigrationError(reason=reason % locals())
- def assert_compute_node_has_enough_disk(self, context, instance_ref, dest,
- disk_over_commit):
- """Checks if destination host has enough disk for block migration.
-
- :param context: security context
- :param instance_ref: nova.db.sqlalchemy.models.Instance object
- :param dest: destination host
- :param disk_over_commit: if True, consider real(not virtual)
- disk size.
-
- """
-
- # Libvirt supports qcow2 disk format,which is usually compressed
- # on compute nodes.
- # Real disk image (compressed) may enlarged to "virtual disk size",
- # that is specified as the maximum disk size.
- # (See qemu-img -f path-to-disk)
- # Scheduler recognizes destination host still has enough disk space
- # if real disk size < available disk size
- # if disk_over_commit is True,
- # otherwise virtual disk size < available disk size.
-
- # Getting total available disk of host
- available_gb = self._get_compute_info(context,
- dest, 'disk_available_least')
- available = available_gb * (1024 ** 3)
-
- # Getting necessary disk size
- ret = self.compute_rpcapi.get_instance_disk_info(context, instance_ref)
- disk_infos = jsonutils.loads(ret)
-
- necessary = 0
- if disk_over_commit:
- for info in disk_infos:
- necessary += int(info['disk_size'])
- else:
- for info in disk_infos:
- necessary += int(info['virt_disk_size'])
-
- # Check that available disk > necessary disk
- if (available - necessary) < 0:
- instance_uuid = instance_ref['uuid']
- reason = _("Unable to migrate %(instance_uuid)s to %(dest)s: "
- "Lack of disk(host:%(available)s "
- "<= instance:%(necessary)s)")
- raise exception.MigrationError(reason=reason % locals())
-
- def _get_compute_info(self, context, host, key):
+ def _get_compute_info(self, context, host):
"""get compute node's information specified by key
:param context: security context
@@ -471,33 +352,4 @@ class Scheduler(object):
"""
compute_node_ref = db.service_get_all_compute_by_host(context, host)
- compute_node_ref = compute_node_ref[0]['compute_node'][0]
- return compute_node_ref[key]
-
- def mounted_on_same_shared_storage(self, context, instance_ref, dest):
- """Check if the src and dest host mount same shared storage.
-
- At first, dest host creates temp file, and src host can see
- it if they mounts same shared storage. Then src host erase it.
-
- :param context: security context
- :param instance_ref: nova.db.sqlalchemy.models.Instance object
- :param dest: destination host
-
- """
-
- src = instance_ref['host']
-
- filename = self.compute_rpcapi.create_shared_storage_test_file(context,
- dest)
-
- try:
- # make sure existence at src host.
- ret = self.compute_rpcapi.check_shared_storage_test_file(context,
- filename, src)
-
- finally:
- self.compute_rpcapi.cleanup_shared_storage_test_file(context,
- filename, dest)
-
- return ret
+ return compute_node_ref[0]['compute_node'][0]
diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py
index 31538b975..28020c762 100644
--- a/nova/tests/compute/test_compute.py
+++ b/nova/tests/compute/test_compute.py
@@ -1379,19 +1379,114 @@ class ComputeTestCase(BaseTestCase):
self.assertEqual(inst_ref['vm_state'], vm_states.ERROR)
self.compute.terminate_instance(context, inst_ref['uuid'])
+ def test_check_can_live_migrate_source_works_correctly(self):
+ """Confirm check_can_live_migrate_source works on positive path"""
+ context = self.context.elevated()
+ inst_ref = self._create_fake_instance({'host': 'fake_host_2'})
+ inst_id = inst_ref["id"]
+ dest = "fake_host_1"
+
+ self.mox.StubOutWithMock(db, 'instance_get')
+ self.mox.StubOutWithMock(self.compute.driver,
+ 'check_can_live_migrate_source')
+
+ dest_check_data = {"test": "data"}
+ db.instance_get(context, inst_id).AndReturn(inst_ref)
+ self.compute.driver.check_can_live_migrate_source(context,
+ inst_ref,
+ dest_check_data)
+
+ self.mox.ReplayAll()
+ self.compute.check_can_live_migrate_source(context, inst_id,
+ dest_check_data)
+
+ def test_check_can_live_migrate_destination_works_correctly(self):
+ """Confirm check_can_live_migrate_destination works on positive path"""
+ context = self.context.elevated()
+ inst_ref = self._create_fake_instance({'host': 'fake_host_2'})
+ inst_id = inst_ref["id"]
+ dest = "fake_host_1"
+
+ self.mox.StubOutWithMock(db, 'instance_get')
+ self.mox.StubOutWithMock(self.compute.driver,
+ 'check_can_live_migrate_destination')
+ self.mox.StubOutWithMock(self.compute.compute_rpcapi,
+ 'check_can_live_migrate_source')
+ self.mox.StubOutWithMock(self.compute.driver,
+ 'check_can_live_migrate_destination_cleanup')
+
+ db.instance_get(context, inst_id).AndReturn(inst_ref)
+ dest_check_data = {"test": "data"}
+ self.compute.driver.check_can_live_migrate_destination(context,
+ inst_ref, True, False).AndReturn(dest_check_data)
+ self.compute.compute_rpcapi.check_can_live_migrate_source(context,
+ inst_ref, dest_check_data)
+ self.compute.driver.check_can_live_migrate_destination_cleanup(
+ context, dest_check_data)
+
+ self.mox.ReplayAll()
+ self.compute.check_can_live_migrate_destination(context, inst_id,
+ True, False)
+
+ def test_check_can_live_migrate_destination_fails_dest_check(self):
+ """Confirm check_can_live_migrate_destination works on positive path"""
+ context = self.context.elevated()
+ inst_ref = self._create_fake_instance({'host': 'fake_host_2'})
+ inst_id = inst_ref["id"]
+ dest = "fake_host_1"
+
+ self.mox.StubOutWithMock(db, 'instance_get')
+ self.mox.StubOutWithMock(self.compute.driver,
+ 'check_can_live_migrate_destination')
+
+ db.instance_get(context, inst_id).AndReturn(inst_ref)
+ self.compute.driver.check_can_live_migrate_destination(context,
+ inst_ref, True, False).AndRaise(exception.Invalid())
+
+ self.mox.ReplayAll()
+ self.assertRaises(exception.Invalid,
+ self.compute.check_can_live_migrate_destination,
+ context, inst_id, True, False)
+
+ def test_check_can_live_migrate_destination_fails_source(self):
+ """Confirm check_can_live_migrate_destination works on positive path"""
+ context = self.context.elevated()
+ inst_ref = self._create_fake_instance({'host': 'fake_host_2'})
+ inst_id = inst_ref["id"]
+ dest = "fake_host_1"
+
+ self.mox.StubOutWithMock(db, 'instance_get')
+ self.mox.StubOutWithMock(self.compute.driver,
+ 'check_can_live_migrate_destination')
+ self.mox.StubOutWithMock(self.compute.compute_rpcapi,
+ 'check_can_live_migrate_source')
+ self.mox.StubOutWithMock(self.compute.driver,
+ 'check_can_live_migrate_destination_cleanup')
+
+ db.instance_get(context, inst_id).AndReturn(inst_ref)
+ dest_check_data = {"test": "data"}
+ self.compute.driver.check_can_live_migrate_destination(context,
+ inst_ref, True, False).AndReturn(dest_check_data)
+ self.compute.compute_rpcapi.check_can_live_migrate_source(context,
+ inst_ref, dest_check_data).AndRaise(exception.Invalid())
+ self.compute.driver.check_can_live_migrate_destination_cleanup(
+ context, dest_check_data)
+
+ self.mox.ReplayAll()
+ self.assertRaises(exception.Invalid,
+ self.compute.check_can_live_migrate_destination,
+ context, inst_id, True, False)
+
def test_pre_live_migration_instance_has_no_fixed_ip(self):
"""Confirm raising exception if instance doesn't have fixed_ip."""
# creating instance testdata
- inst_ref = self._create_fake_instance({'host': 'dummy'})
- c = context.get_admin_context()
+ context = self.context.elevated()
+ inst_ref = self._create_fake_instance()
+ inst_id = inst_ref["id"]
- # start test
- self.stubs.Set(time, 'sleep', lambda t: None)
+ self.mox.ReplayAll()
self.assertRaises(exception.FixedIpNotFoundForInstance,
- self.compute.pre_live_migration,
- c, inst_ref['id'])
- # cleanup
- db.instance_destroy(c, inst_ref['uuid'])
+ self.compute.pre_live_migration, context, inst_id)
def test_pre_live_migration_works_correctly(self):
"""Confirm setup_compute_volume is called when volume is mounted."""
@@ -1400,16 +1495,18 @@ class ComputeTestCase(BaseTestCase):
spectacular=True)
self.stubs.Set(nova.compute.manager.ComputeManager,
'_get_instance_nw_info', stupid)
+
# creating instance testdata
inst_ref = self._create_fake_instance({'host': 'dummy'})
+ inst_id = inst_ref['id']
c = context.get_admin_context()
+ nw_info = fake_network.fake_get_instance_nw_info(self.stubs)
# creating mocks
self.mox.StubOutWithMock(self.compute.driver, 'pre_live_migration')
- self.compute.driver.pre_live_migration({'block_device_mapping': []})
- nw_info = fake_network.fake_get_instance_nw_info(self.stubs)
- self.mox.StubOutWithMock(self.compute.driver, 'plug_vifs')
- self.compute.driver.plug_vifs(mox.IsA(inst_ref), nw_info)
+ self.compute.driver.pre_live_migration(mox.IsA(c), mox.IsA(inst_ref),
+ {'block_device_mapping': []},
+ mox.IgnoreArg())
self.mox.StubOutWithMock(self.compute.driver,
'ensure_filtering_rules_for_instance')
self.compute.driver.ensure_filtering_rules_for_instance(
@@ -1417,7 +1514,7 @@ class ComputeTestCase(BaseTestCase):
# start test
self.mox.ReplayAll()
- ret = self.compute.pre_live_migration(c, inst_ref['id'])
+ ret = self.compute.pre_live_migration(c, inst_id)
self.assertEqual(ret, None)
# cleanup
@@ -1427,13 +1524,15 @@ class ComputeTestCase(BaseTestCase):
"""Confirm exception when pre_live_migration fails."""
# creating instance testdata
inst_ref = self._create_fake_instance({'host': 'dummy'})
+ inst_uuid = inst_ref['uuid']
+ inst_id = inst_ref['id']
c = context.get_admin_context()
topic = rpc.queue_get_for(c, FLAGS.compute_topic, inst_ref['host'])
# creating volume testdata
volume_id = db.volume_create(c, {'size': 1})['id']
- values = {'instance_uuid': inst_ref['uuid'], 'device_name': '/dev/vdc',
+ values = {'instance_uuid': inst_uuid, 'device_name': '/dev/vdc',
'delete_on_termination': False, 'volume_id': volume_id}
db.block_device_mapping_create(c, values)
@@ -1441,60 +1540,60 @@ class ComputeTestCase(BaseTestCase):
self.mox.StubOutWithMock(rpc, 'call')
rpc.call(c, FLAGS.volume_topic,
{"method": "check_for_export",
- "args": {'instance_id': inst_ref['id']}})
+ "args": {'instance_id': inst_id}})
- self.mox.StubOutWithMock(self.compute.driver, 'get_instance_disk_info')
+ self.mox.StubOutWithMock(self.compute.driver,
+ 'get_instance_disk_info')
self.compute.driver.get_instance_disk_info(inst_ref.name)
- rpc.call(c, topic,
- {"method": "pre_live_migration",
- "args": {'instance_id': inst_ref['id'],
- 'block_migration': True,
- 'disk': None},
- "version": compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION
- }, None).AndRaise(rpc.common.RemoteError('', '', ''))
+ self.mox.StubOutWithMock(self.compute.compute_rpcapi,
+ 'pre_live_migration')
+ self.compute.compute_rpcapi.pre_live_migration(c, mox.IsA(inst_ref),
+ True, None, inst_ref['host']).AndRaise(
+ rpc.common.RemoteError('', '', ''))
# mocks for rollback
rpc.call(c, 'network', {'method': 'setup_networks_on_host',
- 'args': {'instance_id': inst_ref['id'],
+ 'args': {'instance_id': inst_id,
'host': self.compute.host,
'teardown': False}})
rpc.call(c, topic,
{"method": "remove_volume_connection",
- "args": {'instance_id': inst_ref['id'],
+ "args": {'instance_id': inst_id,
'volume_id': volume_id},
- "version": compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION},
- None)
- rpc.cast(c, topic, {"method": "rollback_live_migration_at_destination",
- "args": {'instance_id': inst_ref['id']}})
+ "version": "1.0"}, None)
+ rpc.cast(c, topic,
+ {"method": "rollback_live_migration_at_destination",
+ "args": {'instance_id': inst_id},
+ "version": "1.0"})
# start test
self.mox.ReplayAll()
self.assertRaises(rpc_common.RemoteError,
self.compute.live_migration,
- c, inst_ref['id'], inst_ref['host'], True)
+ c, inst_id, inst_ref['host'], True)
# cleanup
for bdms in db.block_device_mapping_get_all_by_instance(
- c, inst_ref['uuid']):
+ c, inst_uuid):
db.block_device_mapping_destroy(c, bdms['id'])
db.volume_destroy(c, volume_id)
- db.instance_destroy(c, inst_ref['uuid'])
+ db.instance_destroy(c, inst_uuid)
def test_live_migration_works_correctly(self):
"""Confirm live_migration() works as expected correctly."""
# creating instance testdata
- instance = self._create_fake_instance({'host': 'dummy'})
- instance_id = instance['id']
c = context.get_admin_context()
- inst_ref = db.instance_get(c, instance_id)
- topic = rpc.queue_get_for(c, FLAGS.compute_topic, inst_ref['host'])
+ inst_ref = self._create_fake_instance({'host': 'dummy'})
+ inst_uuid = inst_ref['uuid']
+ inst_id = inst_ref['id']
# create
self.mox.StubOutWithMock(rpc, 'call')
+ topic = rpc.queue_get_for(c, FLAGS.compute_topic, inst_ref['host'])
rpc.call(c, topic,
{"method": "pre_live_migration",
- "args": {'instance_id': instance_id,
+ "args": {'instance_id': inst_id,
'block_migration': False,
'disk': None},
"version": compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION},
@@ -1502,11 +1601,11 @@ class ComputeTestCase(BaseTestCase):
# start test
self.mox.ReplayAll()
- ret = self.compute.live_migration(c, inst_ref['id'], inst_ref['host'])
+ ret = self.compute.live_migration(c, inst_id, inst_ref['host'])
self.assertEqual(ret, None)
# cleanup
- db.instance_destroy(c, instance['uuid'])
+ db.instance_destroy(c, inst_uuid)
def test_post_live_migration_working_correctly(self):
"""Confirm post_live_migration() works as expected correctly."""
@@ -1515,40 +1614,40 @@ class ComputeTestCase(BaseTestCase):
# creating testdata
c = context.get_admin_context()
- instance = self._create_fake_instance({
- 'state_description': 'migrating',
- 'state': power_state.PAUSED})
- instance_id = instance['id']
- i_ref = db.instance_get(c, instance_id)
- db.instance_update(c, i_ref['uuid'],
+ inst_ref = self._create_fake_instance({
+ 'state_description': 'migrating',
+ 'state': power_state.PAUSED})
+ inst_uuid = inst_ref['uuid']
+ inst_id = inst_ref['id']
+
+ db.instance_update(c, inst_uuid,
{'task_state': task_states.MIGRATING,
'power_state': power_state.PAUSED})
- v_ref = db.volume_create(c, {'size': 1, 'instance_id': instance_id})
+ v_ref = db.volume_create(c, {'size': 1, 'instance_id': inst_id})
fix_addr = db.fixed_ip_create(c, {'address': '1.1.1.1',
- 'instance_id': instance_id})
+ 'instance_id': inst_id})
fix_ref = db.fixed_ip_get_by_address(c, fix_addr)
db.floating_ip_create(c, {'address': flo_addr,
'fixed_ip_id': fix_ref['id']})
# creating mocks
self.mox.StubOutWithMock(self.compute.driver, 'unfilter_instance')
- self.compute.driver.unfilter_instance(i_ref, [])
+ self.compute.driver.unfilter_instance(inst_ref, [])
self.mox.StubOutWithMock(rpc, 'call')
rpc.call(c, rpc.queue_get_for(c, FLAGS.compute_topic, dest),
{"method": "post_live_migration_at_destination",
- "args": {'instance_id': i_ref['id'], 'block_migration': False},
- "version": compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION},
- None)
+ "args": {'instance_id': inst_id, 'block_migration': False},
+ "version": "1.0"}, None)
self.mox.StubOutWithMock(self.compute.driver, 'unplug_vifs')
- self.compute.driver.unplug_vifs(i_ref, [])
+ self.compute.driver.unplug_vifs(inst_ref, [])
rpc.call(c, 'network', {'method': 'setup_networks_on_host',
- 'args': {'instance_id': instance_id,
+ 'args': {'instance_id': inst_id,
'host': self.compute.host,
'teardown': True}})
# start test
self.mox.ReplayAll()
- self.compute.post_live_migration(c, i_ref, dest)
+ self.compute.post_live_migration(c, inst_ref, dest)
# make sure floating ips are rewritten to destinatioin hostname.
flo_refs = db.floating_ip_get_all_by_host(c, dest)
@@ -1556,7 +1655,7 @@ class ComputeTestCase(BaseTestCase):
self.assertEqual(flo_refs[0]['address'], flo_addr)
# cleanup
- db.instance_destroy(c, instance['uuid'])
+ db.instance_destroy(c, inst_uuid)
db.volume_destroy(c, v_ref['id'])
db.floating_ip_destroy(c, flo_addr)
diff --git a/nova/tests/compute/test_rpcapi.py b/nova/tests/compute/test_rpcapi.py
index fbaf3b4d7..40998b593 100644
--- a/nova/tests/compute/test_rpcapi.py
+++ b/nova/tests/compute/test_rpcapi.py
@@ -60,12 +60,16 @@ class ComputeRpcAPITestCase(test.TestCase):
expected_msg['args']['host'] = host_param
elif 'host' in expected_msg['args']:
del expected_msg['args']['host']
+ if 'destination' in expected_msg['args']:
+ del expected_msg['args']['destination']
if 'instance' in expected_msg['args']:
instance = expected_msg['args']['instance']
del expected_msg['args']['instance']
if method in ['rollback_live_migration_at_destination',
'pre_live_migration', 'remove_volume_connection',
- 'post_live_migration_at_destination']:
+ 'post_live_migration_at_destination',
+ 'check_can_live_migrate_destination',
+ 'check_can_live_migrate_source']:
expected_msg['args']['instance_id'] = instance['id']
elif method == 'get_instance_disk_info':
expected_msg['args']['instance_name'] = instance['name']
@@ -78,6 +82,8 @@ class ComputeRpcAPITestCase(test.TestCase):
kwargs['cast'] = False
if 'host' in kwargs:
host = kwargs['host']
+ elif 'destination' in kwargs:
+ host = kwargs['destination']
else:
host = kwargs['instance']['host']
expected_topic = '%s.%s' % (FLAGS.compute_topic, host)
@@ -112,17 +118,15 @@ class ComputeRpcAPITestCase(test.TestCase):
self._test_compute_api('attach_volume', 'cast',
instance=self.fake_instance, volume_id='id', mountpoint='mp')
- def test_check_shared_storage_test_file(self):
- self._test_compute_api('check_shared_storage_test_file', 'call',
- filename='fn', host='host')
+ def test_check_can_live_migrate_destination(self):
+ self._test_compute_api('check_can_live_migrate_destination', 'call',
+ version='1.2', instance=self.fake_instance, destination='dest',
+ block_migration=True, disk_over_commit=True)
- def test_cleanup_shared_storage_test_file(self):
- self._test_compute_api('cleanup_shared_storage_test_file', 'cast',
- filename='fn', host='host')
-
- def test_compare_cpu(self):
- self._test_compute_api('compare_cpu', 'call', cpu_info='info',
- host='host')
+ def test_check_can_live_migrate_source(self):
+ self._test_compute_api('check_can_live_migrate_source', 'call',
+ version='1.2', instance=self.fake_instance,
+ dest_check_data={"test": "data"})
def test_confirm_resize_cast(self):
self._test_compute_api('confirm_resize', 'cast',
@@ -132,10 +136,6 @@ class ComputeRpcAPITestCase(test.TestCase):
self._test_compute_api('confirm_resize', 'call',
instance=self.fake_instance, migration_id='id', host='host')
- def test_create_shared_storage_test_file(self):
- self._test_compute_api('create_shared_storage_test_file', 'call',
- host='host')
-
def test_detach_volume(self):
self._test_compute_api('detach_volume', 'cast',
instance=self.fake_instance, volume_id='id')
diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py
index 61d089b3b..38bdeeb40 100644
--- a/nova/tests/scheduler/test_scheduler.py
+++ b/nova/tests/scheduler/test_scheduler.py
@@ -28,6 +28,7 @@ from nova import context
from nova import db
from nova import exception
from nova import flags
+from nova import notifications
from nova.openstack.common import jsonutils
from nova.openstack.common import rpc
from nova.openstack.common.rpc import common as rpc_common
@@ -440,23 +441,32 @@ class SchedulerTestCase(test.TestCase):
self.mox.StubOutWithMock(self.driver, '_live_migration_src_check')
self.mox.StubOutWithMock(self.driver, '_live_migration_dest_check')
self.mox.StubOutWithMock(self.driver, '_live_migration_common_check')
+ self.mox.StubOutWithMock(self.driver.compute_rpcapi,
+ 'check_can_live_migrate_destination')
self.mox.StubOutWithMock(db, 'instance_update_and_get_original')
self.mox.StubOutWithMock(driver, 'cast_to_compute_host')
+ self.mox.StubOutWithMock(notifications, 'send_update')
dest = 'fake_host2'
block_migration = False
disk_over_commit = False
instance = self._live_migration_instance()
- db.instance_get(self.context, instance['id']).AndReturn(instance)
+ instance_id = instance['id']
+ instance_uuid = instance['uuid']
+ db.instance_get(self.context,
+ instance_id).AndReturn(instance)
self.driver._live_migration_src_check(self.context, instance)
- self.driver._live_migration_dest_check(self.context, instance,
- dest, block_migration, disk_over_commit)
+ self.driver._live_migration_dest_check(self.context, instance, dest)
self.driver._live_migration_common_check(self.context, instance,
- dest, block_migration, disk_over_commit)
- db.instance_update_and_get_original(self.context, instance['id'],
+ dest)
+ self.driver.compute_rpcapi.check_can_live_migrate_destination(
+ self.context, instance, dest, block_migration, disk_over_commit)
+ db.instance_update_and_get_original(self.context, instance_uuid,
{"task_state": task_states.MIGRATING}).AndReturn(
(instance, instance))
+ notifications.send_update(self.context, instance, instance,
+ service="scheduler")
driver.cast_to_compute_host(self.context, instance['host'],
'live_migration', update_db=False,
@@ -469,38 +479,13 @@ class SchedulerTestCase(test.TestCase):
block_migration=block_migration,
disk_over_commit=disk_over_commit)
- def _check_shared_storage(self, dest, instance, check_result):
- tmp_filename = 'test-filename'
- rpc.queue_get_for(self.context, FLAGS.compute_topic,
- dest).AndReturn('dest_queue')
- rpc.call(self.context, 'dest_queue',
- {'method': 'create_shared_storage_test_file',
- 'args': {},
- 'version': compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION},
- None).AndReturn(tmp_filename)
- rpc.queue_get_for(self.context, FLAGS.compute_topic,
- instance['host']).AndReturn('src_queue')
- rpc.call(self.context, 'src_queue',
- {'method': 'check_shared_storage_test_file',
- 'args': {'filename': tmp_filename},
- 'version': compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION},
- None).AndReturn(check_result)
- rpc.queue_get_for(self.context, FLAGS.compute_topic,
- dest).AndReturn('dest_queue')
- rpc.cast(self.context, 'dest_queue',
- {'method': 'cleanup_shared_storage_test_file',
- 'args': {'filename': tmp_filename},
- 'version': compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION})
-
def test_live_migration_all_checks_pass(self):
"""Test live migration when all checks pass."""
self.mox.StubOutWithMock(db, 'instance_get')
self.mox.StubOutWithMock(utils, 'service_is_up')
self.mox.StubOutWithMock(db, 'service_get_all_compute_by_host')
- self.mox.StubOutWithMock(self.driver, '_get_compute_info')
self.mox.StubOutWithMock(db, 'instance_get_all_by_host')
- self.mox.StubOutWithMock(rpc, 'queue_get_for')
self.mox.StubOutWithMock(rpc, 'call')
self.mox.StubOutWithMock(rpc, 'cast')
self.mox.StubOutWithMock(db, 'instance_update_and_get_original')
@@ -510,8 +495,12 @@ class SchedulerTestCase(test.TestCase):
block_migration = True
disk_over_commit = True
instance = self._live_migration_instance()
- db.instance_get(self.context, instance['id']).AndReturn(instance)
+ instance_id = instance['id']
+ instance_uuid = instance['uuid']
+ db.instance_get(self.context,
+ instance_id).AndReturn(instance)
+ # Source checks
db.service_get_all_compute_by_host(self.context,
instance['host']).AndReturn(['fake_service2'])
utils.service_is_up('fake_service2').AndReturn(True)
@@ -521,60 +510,42 @@ class SchedulerTestCase(test.TestCase):
dest).AndReturn(['fake_service3'])
utils.service_is_up('fake_service3').AndReturn(True)
# assert_compute_node_has_enough_memory()
- self.driver._get_compute_info(self.context, dest,
- 'memory_mb').AndReturn(2048)
+ db.service_get_all_compute_by_host(self.context, dest).AndReturn(
+ [{'compute_node': [{'memory_mb': 2048,
+ 'hypervisor_version': 1}]}])
db.instance_get_all_by_host(self.context, dest).AndReturn(
[dict(memory_mb=256), dict(memory_mb=512)])
- # assert_compute_node_has_enough_disk()
- self.driver._get_compute_info(self.context, dest,
- 'disk_available_least').AndReturn(1025)
- rpc.queue_get_for(self.context, FLAGS.compute_topic,
- instance['host']).AndReturn('src_queue1')
- instance_disk_info_msg = {
- 'method': 'get_instance_disk_info',
- 'args': {
- 'instance_name': instance['name'],
- },
- 'version': compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION,
- }
- instance_disk_info = [{'disk_size': 1024 * (1024 ** 3)}]
- rpc.call(self.context,
- 'src_queue1',
- instance_disk_info_msg,
- None).AndReturn(jsonutils.dumps(instance_disk_info))
-
- # Common checks (shared storage ok, same hypervisor, etc)
- self._check_shared_storage(dest, instance, False)
+ # Common checks (same hypervisor, etc)
db.service_get_all_compute_by_host(self.context, dest).AndReturn(
[{'compute_node': [{'hypervisor_type': 'xen',
'hypervisor_version': 1}]}])
- # newer hypervisor version for src
db.service_get_all_compute_by_host(self.context,
instance['host']).AndReturn(
[{'compute_node': [{'hypervisor_type': 'xen',
'hypervisor_version': 1,
'cpu_info': 'fake_cpu_info'}]}])
- rpc.queue_get_for(self.context, FLAGS.compute_topic,
- dest).AndReturn('dest_queue')
- rpc.call(self.context, 'dest_queue',
- {'method': 'compare_cpu',
- 'args': {'cpu_info': 'fake_cpu_info'},
- 'version': compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION},
- None).AndReturn(True)
-
- db.instance_update_and_get_original(self.context, instance['id'],
+
+ rpc.call(self.context, "compute.fake_host2",
+ {"method": 'check_can_live_migrate_destination',
+ "args": {'instance_id': instance_id,
+ 'block_migration': block_migration,
+ 'disk_over_commit': disk_over_commit},
+ "version": "1.2"},
+ None)
+
+ db.instance_update_and_get_original(self.context, instance_uuid,
{"task_state": task_states.MIGRATING}).AndReturn(
(instance, instance))
driver.cast_to_compute_host(self.context, instance['host'],
'live_migration', update_db=False,
- instance_id=instance['id'], dest=dest,
+ instance_id=instance_id, dest=dest,
block_migration=block_migration)
self.mox.ReplayAll()
result = self.driver.schedule_live_migration(self.context,
- instance_id=instance['id'], dest=dest,
+ instance_id=instance_id, dest=dest,
block_migration=block_migration,
disk_over_commit=disk_over_commit)
self.assertEqual(result, None)
@@ -587,17 +558,44 @@ class SchedulerTestCase(test.TestCase):
dest = 'fake_host2'
block_migration = False
instance = self._live_migration_instance()
+ instance_id = instance['id']
instance['power_state'] = power_state.NOSTATE
- db.instance_get(self.context, instance['id']).AndReturn(instance)
+ db.instance_get(self.context,
+ instance_id).AndReturn(instance)
self.mox.ReplayAll()
self.assertRaises(exception.InstanceNotRunning,
self.driver.schedule_live_migration, self.context,
- instance_id=instance['id'], dest=dest,
+ instance_id=instance_id, dest=dest,
block_migration=block_migration)
+ def test_live_migration_compute_src_not_exist(self):
+ """Raise exception when src compute node is does not exist."""
+
+ self.mox.StubOutWithMock(db, 'instance_get')
+ self.mox.StubOutWithMock(utils, 'service_is_up')
+ self.mox.StubOutWithMock(db, 'service_get_all_compute_by_host')
+
+ dest = 'fake_host2'
+ block_migration = False
+ instance = self._live_migration_instance()
+ instance_id = instance['id']
+ db.instance_get(self.context,
+ instance_id).AndReturn(instance)
+
+ # Compute down
+ db.service_get_all_compute_by_host(self.context,
+ instance['host']).AndRaise(
+ exception.NotFound())
+
+ self.mox.ReplayAll()
+ self.assertRaises(exception.ComputeServiceUnavailable,
+ self.driver.schedule_live_migration, self.context,
+ instance_id=instance_id, dest=dest,
+ block_migration=block_migration)
+
def test_live_migration_compute_src_not_alive(self):
"""Raise exception when src compute node is not alive."""
@@ -608,7 +606,9 @@ class SchedulerTestCase(test.TestCase):
dest = 'fake_host2'
block_migration = False
instance = self._live_migration_instance()
- db.instance_get(self.context, instance['id']).AndReturn(instance)
+ instance_id = instance['id']
+ db.instance_get(self.context,
+ instance_id).AndReturn(instance)
# Compute down
db.service_get_all_compute_by_host(self.context,
@@ -618,7 +618,7 @@ class SchedulerTestCase(test.TestCase):
self.mox.ReplayAll()
self.assertRaises(exception.ComputeServiceUnavailable,
self.driver.schedule_live_migration, self.context,
- instance_id=instance['id'], dest=dest,
+ instance_id=instance_id, dest=dest,
block_migration=block_migration)
def test_live_migration_compute_dest_not_alive(self):
@@ -632,7 +632,9 @@ class SchedulerTestCase(test.TestCase):
dest = 'fake_host2'
block_migration = False
instance = self._live_migration_instance()
- db.instance_get(self.context, instance['id']).AndReturn(instance)
+ instance_id = instance['id']
+ db.instance_get(self.context,
+ instance_id).AndReturn(instance)
self.driver._live_migration_src_check(self.context, instance)
db.service_get_all_compute_by_host(self.context,
@@ -643,7 +645,7 @@ class SchedulerTestCase(test.TestCase):
self.mox.ReplayAll()
self.assertRaises(exception.ComputeServiceUnavailable,
self.driver.schedule_live_migration, self.context,
- instance_id=instance['id'], dest=dest,
+ instance_id=instance_id, dest=dest,
block_migration=block_migration)
def test_live_migration_dest_check_service_same_host(self):
@@ -657,10 +659,12 @@ class SchedulerTestCase(test.TestCase):
block_migration = False
disk_over_commit = False
instance = self._live_migration_instance()
+ instance_id = instance['id']
# make dest same as src
dest = instance['host']
- db.instance_get(self.context, instance['id']).AndReturn(instance)
+ db.instance_get(self.context,
+ instance_id).AndReturn(instance)
self.driver._live_migration_src_check(self.context, instance)
db.service_get_all_compute_by_host(self.context,
@@ -670,7 +674,7 @@ class SchedulerTestCase(test.TestCase):
self.mox.ReplayAll()
self.assertRaises(exception.UnableToMigrateToSelf,
self.driver.schedule_live_migration, self.context,
- instance_id=instance['id'], dest=dest,
+ instance_id=instance_id, dest=dest,
block_migration=block_migration,
disk_over_commit=False)
@@ -688,138 +692,29 @@ class SchedulerTestCase(test.TestCase):
block_migration = False
disk_over_commit = False
instance = self._live_migration_instance()
- db.instance_get(self.context, instance['id']).AndReturn(instance)
+ instance_id = instance['id']
+ db.instance_get(self.context,
+ instance_id).AndReturn(instance)
self.driver._live_migration_src_check(self.context, instance)
db.service_get_all_compute_by_host(self.context,
dest).AndReturn(['fake_service3'])
utils.service_is_up('fake_service3').AndReturn(True)
- self.driver._get_compute_info(self.context, dest,
- 'memory_mb').AndReturn(2048)
+ self.driver._get_compute_info(self.context, dest).AndReturn(
+ {'memory_mb': 2048})
db.instance_get_all_by_host(self.context, dest).AndReturn(
[dict(memory_mb=1024), dict(memory_mb=512)])
self.mox.ReplayAll()
self.assertRaises(exception.MigrationError,
self.driver.schedule_live_migration, self.context,
- instance_id=instance['id'], dest=dest,
- block_migration=block_migration,
- disk_over_commit=disk_over_commit)
-
- def test_block_migration_dest_check_service_lack_disk(self):
- """Confirms exception raises when dest doesn't have enough disk."""
-
- self.mox.StubOutWithMock(db, 'instance_get')
- self.mox.StubOutWithMock(self.driver, '_live_migration_src_check')
- self.mox.StubOutWithMock(db, 'service_get_all_compute_by_host')
- self.mox.StubOutWithMock(utils, 'service_is_up')
- self.mox.StubOutWithMock(self.driver,
- 'assert_compute_node_has_enough_memory')
- self.mox.StubOutWithMock(self.driver, '_get_compute_info')
- self.mox.StubOutWithMock(db, 'instance_get_all_by_host')
- self.mox.StubOutWithMock(rpc, 'queue_get_for')
- self.mox.StubOutWithMock(rpc, 'call')
-
- dest = 'fake_host2'
- block_migration = True
- disk_over_commit = True
- instance = self._live_migration_instance()
- db.instance_get(self.context, instance['id']).AndReturn(instance)
-
- self.driver._live_migration_src_check(self.context, instance)
- db.service_get_all_compute_by_host(self.context,
- dest).AndReturn(['fake_service3'])
- utils.service_is_up('fake_service3').AndReturn(True)
-
- # Enough memory
- self.driver.assert_compute_node_has_enough_memory(self.context,
- instance, dest)
-
- # Not enough disk
- self.driver._get_compute_info(self.context, dest,
- 'disk_available_least').AndReturn(1023)
- rpc.queue_get_for(self.context, FLAGS.compute_topic,
- instance['host']).AndReturn('src_queue')
- instance_disk_info_msg = {
- 'method': 'get_instance_disk_info',
- 'args': {
- 'instance_name': instance['name'],
- },
- 'version': compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION,
- }
- instance_disk_info = [{'disk_size': 1024 * (1024 ** 3)}]
- rpc.call(self.context,
- 'src_queue',
- instance_disk_info_msg,
- None).AndReturn(jsonutils.dumps(instance_disk_info))
-
- self.mox.ReplayAll()
- self.assertRaises(exception.MigrationError,
- self.driver.schedule_live_migration, self.context,
- instance_id=instance['id'], dest=dest,
- block_migration=block_migration,
- disk_over_commit=disk_over_commit)
-
- def test_live_migration_different_shared_storage_raises(self):
- """Src and dest must have same shared storage for live migration"""
-
- self.mox.StubOutWithMock(db, 'instance_get')
- self.mox.StubOutWithMock(self.driver, '_live_migration_src_check')
- self.mox.StubOutWithMock(self.driver, '_live_migration_dest_check')
- self.mox.StubOutWithMock(rpc, 'queue_get_for')
- self.mox.StubOutWithMock(rpc, 'call')
- self.mox.StubOutWithMock(rpc, 'cast')
-
- dest = 'fake_host2'
- block_migration = False
- disk_over_commit = False
- instance = self._live_migration_instance()
- db.instance_get(self.context, instance['id']).AndReturn(instance)
-
- self.driver._live_migration_src_check(self.context, instance)
- self.driver._live_migration_dest_check(self.context, instance,
- dest, block_migration, disk_over_commit)
-
- self._check_shared_storage(dest, instance, False)
-
- self.mox.ReplayAll()
- self.assertRaises(exception.InvalidSharedStorage,
- self.driver.schedule_live_migration, self.context,
- instance_id=instance['id'], dest=dest,
- block_migration=block_migration,
- disk_over_commit=disk_over_commit)
-
- def test_live_migration_same_shared_storage_okay(self):
- """live migration works with same src and dest shared storage"""
-
- self.mox.StubOutWithMock(db, 'instance_get')
- self.mox.StubOutWithMock(self.driver, '_live_migration_src_check')
- self.mox.StubOutWithMock(self.driver, '_live_migration_dest_check')
- self.mox.StubOutWithMock(rpc, 'queue_get_for')
- self.mox.StubOutWithMock(rpc, 'call')
- self.mox.StubOutWithMock(rpc, 'cast')
-
- dest = 'fake_host2'
- block_migration = False
- disk_over_commit = False
- instance = self._live_migration_instance()
- db.instance_get(self.context, instance['id']).AndReturn(instance)
-
- self.driver._live_migration_src_check(self.context, instance)
- self.driver._live_migration_dest_check(self.context, instance,
- dest, block_migration, disk_over_commit)
-
- self._check_shared_storage(dest, instance, False)
-
- self.mox.ReplayAll()
- self.assertRaises(exception.InvalidSharedStorage,
- self.driver.schedule_live_migration, self.context,
- instance_id=instance['id'], dest=dest,
+ instance_id=instance_id, dest=dest,
block_migration=block_migration,
disk_over_commit=disk_over_commit)
def test_live_migration_different_hypervisor_type_raises(self):
+ """Confirm live_migration to hypervisor of different type raises"""
self.mox.StubOutWithMock(db, 'instance_get')
self.mox.StubOutWithMock(self.driver, '_live_migration_src_check')
self.mox.StubOutWithMock(self.driver, '_live_migration_dest_check')
@@ -832,18 +727,16 @@ class SchedulerTestCase(test.TestCase):
block_migration = False
disk_over_commit = False
instance = self._live_migration_instance()
- db.instance_get(self.context, instance['id']).AndReturn(instance)
+ instance_id = instance['id']
+ db.instance_get(self.context,
+ instance_id).AndReturn(instance)
self.driver._live_migration_src_check(self.context, instance)
- self.driver._live_migration_dest_check(self.context, instance,
- dest, block_migration, disk_over_commit)
-
- self._check_shared_storage(dest, instance, True)
+ self.driver._live_migration_dest_check(self.context, instance, dest)
db.service_get_all_compute_by_host(self.context, dest).AndReturn(
[{'compute_node': [{'hypervisor_type': 'xen',
'hypervisor_version': 1}]}])
- # different hypervisor type
db.service_get_all_compute_by_host(self.context,
instance['host']).AndReturn(
[{'compute_node': [{'hypervisor_type': 'not-xen',
@@ -852,11 +745,12 @@ class SchedulerTestCase(test.TestCase):
self.mox.ReplayAll()
self.assertRaises(exception.InvalidHypervisorType,
self.driver.schedule_live_migration, self.context,
- instance_id=instance['id'], dest=dest,
+ instance_id=instance_id, dest=dest,
block_migration=block_migration,
disk_over_commit=disk_over_commit)
def test_live_migration_dest_hypervisor_version_older_raises(self):
+ """Confirm live migration to older hypervisor raises"""
self.mox.StubOutWithMock(db, 'instance_get')
self.mox.StubOutWithMock(self.driver, '_live_migration_src_check')
self.mox.StubOutWithMock(self.driver, '_live_migration_dest_check')
@@ -869,18 +763,16 @@ class SchedulerTestCase(test.TestCase):
block_migration = False
disk_over_commit = False
instance = self._live_migration_instance()
- db.instance_get(self.context, instance['id']).AndReturn(instance)
+ instance_id = instance['id']
+ db.instance_get(self.context,
+ instance_id).AndReturn(instance)
self.driver._live_migration_src_check(self.context, instance)
- self.driver._live_migration_dest_check(self.context, instance,
- dest, block_migration, disk_over_commit)
-
- self._check_shared_storage(dest, instance, True)
+ self.driver._live_migration_dest_check(self.context, instance, dest)
db.service_get_all_compute_by_host(self.context, dest).AndReturn(
[{'compute_node': [{'hypervisor_type': 'xen',
'hypervisor_version': 1}]}])
- # newer hypervisor version for src
db.service_get_all_compute_by_host(self.context,
instance['host']).AndReturn(
[{'compute_node': [{'hypervisor_type': 'xen',
@@ -888,53 +780,10 @@ class SchedulerTestCase(test.TestCase):
self.mox.ReplayAll()
self.assertRaises(exception.DestinationHypervisorTooOld,
self.driver.schedule_live_migration, self.context,
- instance_id=instance['id'], dest=dest,
+ instance_id=instance_id, dest=dest,
block_migration=block_migration,
disk_over_commit=disk_over_commit)
- def test_live_migration_dest_host_incompatable_cpu_raises(self):
- self.mox.StubOutWithMock(db, 'instance_get')
- self.mox.StubOutWithMock(self.driver, '_live_migration_src_check')
- self.mox.StubOutWithMock(self.driver, '_live_migration_dest_check')
- self.mox.StubOutWithMock(rpc, 'queue_get_for')
- self.mox.StubOutWithMock(rpc, 'call')
- self.mox.StubOutWithMock(rpc, 'cast')
- self.mox.StubOutWithMock(db, 'service_get_all_compute_by_host')
-
- dest = 'fake_host2'
- block_migration = False
- disk_over_commit = False
- instance = self._live_migration_instance()
- db.instance_get(self.context, instance['id']).AndReturn(instance)
-
- self.driver._live_migration_src_check(self.context, instance)
- self.driver._live_migration_dest_check(self.context, instance,
- dest, block_migration, disk_over_commit)
-
- self._check_shared_storage(dest, instance, True)
-
- db.service_get_all_compute_by_host(self.context, dest).AndReturn(
- [{'compute_node': [{'hypervisor_type': 'xen',
- 'hypervisor_version': 1}]}])
- db.service_get_all_compute_by_host(self.context,
- instance['host']).AndReturn(
- [{'compute_node': [{'hypervisor_type': 'xen',
- 'hypervisor_version': 1,
- 'cpu_info': 'fake_cpu_info'}]}])
- rpc.queue_get_for(self.context, FLAGS.compute_topic,
- dest).AndReturn('dest_queue')
- rpc.call(self.context, 'dest_queue',
- {'method': 'compare_cpu',
- 'args': {'cpu_info': 'fake_cpu_info'},
- 'version': compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION},
- None).AndRaise(rpc_common.RemoteError())
-
- self.mox.ReplayAll()
- self.assertRaises(rpc_common.RemoteError,
- self.driver.schedule_live_migration, self.context,
- instance_id=instance['id'], dest=dest,
- block_migration=block_migration)
-
class SchedulerDriverBaseTestCase(SchedulerTestCase):
"""Test cases for base scheduler driver class methods
diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py
index 433e0e550..16dcd338b 100644
--- a/nova/tests/test_libvirt.py
+++ b/nova/tests/test_libvirt.py
@@ -30,6 +30,8 @@ from xml.dom import minidom
from nova.api.ec2 import cloud
from nova.compute import instance_types
from nova.compute import power_state
+from nova.compute import rpcapi as compute_rpcapi
+from nova.compute import utils as compute_utils
from nova.compute import vm_states
from nova import context
from nova import db
@@ -1483,6 +1485,166 @@ class LibvirtConnTestCase(test.TestCase):
db.instance_destroy(self.context, instance_ref['uuid'])
+ def test_check_can_live_migrate_dest_all_pass_with_block_migration(self):
+ instance_ref = db.instance_create(self.context, self.test_instance)
+ dest = "fake_host_2"
+ src = instance_ref['host']
+ conn = libvirt_driver.LibvirtDriver(False)
+
+ self.mox.StubOutWithMock(conn, '_get_compute_info')
+ self.mox.StubOutWithMock(conn, 'get_instance_disk_info')
+ self.mox.StubOutWithMock(conn, '_create_shared_storage_test_file')
+ self.mox.StubOutWithMock(conn, '_compare_cpu')
+
+ conn._get_compute_info(self.context, FLAGS.host).AndReturn(
+ {'disk_available_least': 400})
+ conn.get_instance_disk_info(instance_ref["name"]).AndReturn(
+ '[{"virt_disk_size":2}]')
+ # _check_cpu_match
+ conn._get_compute_info(self.context,
+ src).AndReturn({'cpu_info': "asdf"})
+ conn._compare_cpu("asdf")
+
+ # mounted_on_same_shared_storage
+ filename = "file"
+ conn._create_shared_storage_test_file().AndReturn(filename)
+
+ self.mox.ReplayAll()
+ return_value = conn.check_can_live_migrate_destination(self.context,
+ instance_ref, True, False)
+ self.assertDictMatch(return_value,
+ {"filename": "file", "block_migration": True})
+
+ def test_check_can_live_migrate_dest_all_pass_no_block_migration(self):
+ instance_ref = db.instance_create(self.context, self.test_instance)
+ dest = "fake_host_2"
+ src = instance_ref['host']
+ conn = libvirt_driver.LibvirtDriver(False)
+
+ self.mox.StubOutWithMock(conn, '_get_compute_info')
+ self.mox.StubOutWithMock(conn, '_create_shared_storage_test_file')
+ self.mox.StubOutWithMock(conn, '_compare_cpu')
+
+ # _check_cpu_match
+ conn._get_compute_info(self.context,
+ src).AndReturn({'cpu_info': "asdf"})
+ conn._compare_cpu("asdf")
+
+ # mounted_on_same_shared_storage
+ filename = "file"
+ conn._create_shared_storage_test_file().AndReturn(filename)
+
+ self.mox.ReplayAll()
+ return_value = conn.check_can_live_migrate_destination(self.context,
+ instance_ref, False, False)
+ self.assertDictMatch(return_value,
+ {"filename": "file", "block_migration": False})
+
+ def test_check_can_live_migrate_dest_fails_not_enough_disk(self):
+ instance_ref = db.instance_create(self.context, self.test_instance)
+ dest = "fake_host_2"
+ src = instance_ref['host']
+ conn = libvirt_driver.LibvirtDriver(False)
+
+ self.mox.StubOutWithMock(conn, '_get_compute_info')
+ self.mox.StubOutWithMock(conn, 'get_instance_disk_info')
+
+ conn._get_compute_info(self.context, FLAGS.host).AndReturn(
+ {'disk_available_least': 0})
+ conn.get_instance_disk_info(instance_ref["name"]).AndReturn(
+ '[{"virt_disk_size":2}]')
+
+ self.mox.ReplayAll()
+ self.assertRaises(exception.MigrationError,
+ conn.check_can_live_migrate_destination,
+ self.context, instance_ref, True, False)
+
+ def test_check_can_live_migrate_dest_incompatible_cpu_raises(self):
+ instance_ref = db.instance_create(self.context, self.test_instance)
+ dest = "fake_host_2"
+ src = instance_ref['host']
+ conn = libvirt_driver.LibvirtDriver(False)
+
+ self.mox.StubOutWithMock(conn, '_get_compute_info')
+ self.mox.StubOutWithMock(conn, '_compare_cpu')
+
+ conn._get_compute_info(self.context, src).AndReturn(
+ {'cpu_info': "asdf"})
+ conn._compare_cpu("asdf").AndRaise(exception.InvalidCPUInfo)
+
+ self.mox.ReplayAll()
+ self.assertRaises(exception.InvalidCPUInfo,
+ conn.check_can_live_migrate_destination,
+ self.context, instance_ref, False, False)
+
+ def test_check_can_live_migrate_dest_fail_space_with_block_migration(self):
+ instance_ref = db.instance_create(self.context, self.test_instance)
+ dest = "fake_host_2"
+ src = instance_ref['host']
+ conn = libvirt_driver.LibvirtDriver(False)
+
+ self.mox.StubOutWithMock(conn, '_get_compute_info')
+ self.mox.StubOutWithMock(conn, 'get_instance_disk_info')
+
+ conn._get_compute_info(self.context, FLAGS.host).AndReturn(
+ {'disk_available_least': 0})
+ conn.get_instance_disk_info(instance_ref["name"]).AndReturn(
+ '[{"virt_disk_size":2}]')
+
+ self.mox.ReplayAll()
+ self.assertRaises(exception.MigrationError,
+ conn.check_can_live_migrate_destination,
+ self.context, instance_ref, True, False)
+
+ def test_check_can_live_migrate_dest_cleanup_works_correctly(self):
+ dest_check_data = {"filename": "file", "block_migration": True}
+ conn = libvirt_driver.LibvirtDriver(False)
+
+ self.mox.StubOutWithMock(conn, '_cleanup_shared_storage_test_file')
+ conn._cleanup_shared_storage_test_file("file")
+
+ self.mox.ReplayAll()
+ conn.check_can_live_migrate_destination_cleanup(self.context,
+ dest_check_data)
+
+ def test_check_can_live_migrate_source_works_correctly(self):
+ instance_ref = db.instance_create(self.context, self.test_instance)
+ dest_check_data = {"filename": "file", "block_migration": True}
+ conn = libvirt_driver.LibvirtDriver(False)
+
+ self.mox.StubOutWithMock(conn, "_check_shared_storage_test_file")
+ conn._check_shared_storage_test_file("file").AndReturn(False)
+
+ self.mox.ReplayAll()
+ conn.check_can_live_migrate_source(self.context, instance_ref,
+ dest_check_data)
+
+ def test_check_can_live_migrate_dest_fail_shared_storage_with_blockm(self):
+ instance_ref = db.instance_create(self.context, self.test_instance)
+ dest_check_data = {"filename": "file", "block_migration": True}
+ conn = libvirt_driver.LibvirtDriver(False)
+
+ self.mox.StubOutWithMock(conn, "_check_shared_storage_test_file")
+ conn._check_shared_storage_test_file("file").AndReturn(True)
+
+ self.mox.ReplayAll()
+ self.assertRaises(exception.InvalidSharedStorage,
+ conn.check_can_live_migrate_source,
+ self.context, instance_ref, dest_check_data)
+
+ def test_check_can_live_migrate_no_shared_storage_no_blck_mig_raises(self):
+ instance_ref = db.instance_create(self.context, self.test_instance)
+ dest_check_data = {"filename": "file", "block_migration": False}
+ conn = libvirt_driver.LibvirtDriver(False)
+
+ self.mox.StubOutWithMock(conn, "_check_shared_storage_test_file")
+ conn._check_shared_storage_test_file("file").AndReturn(False)
+
+ self.mox.ReplayAll()
+ self.assertRaises(exception.InvalidSharedStorage,
+ conn.check_can_live_migrate_source,
+ self.context, instance_ref, dest_check_data)
+
def test_live_migration_raises_exception(self):
"""Confirms recover method is called when exceptions are raised."""
# Preparing data
@@ -1535,7 +1697,7 @@ class LibvirtConnTestCase(test.TestCase):
db.volume_destroy(self.context, volume_ref['id'])
db.instance_destroy(self.context, instance_ref['uuid'])
- def test_pre_live_migration_works_correctly(self):
+ def test_pre_live_migration_works_correctly_mocked(self):
"""Confirms pre_block_migration works correctly."""
# Creating testdata
vol = {'block_device_mapping': [
@@ -1543,6 +1705,14 @@ class LibvirtConnTestCase(test.TestCase):
{'connection_info': 'dummy', 'mount_device': '/dev/sdb'}]}
conn = libvirt_driver.LibvirtDriver(False)
+ class FakeNetworkInfo():
+ def fixed_ips(self):
+ return ["test_ip_addr"]
+
+ inst_ref = {'id': 'foo'}
+ c = context.get_admin_context()
+ nw_info = FakeNetworkInfo()
+
# Creating mocks
self.mox.StubOutWithMock(driver, "block_device_info_get_mapping")
driver.block_device_info_get_mapping(vol
@@ -1552,10 +1722,12 @@ class LibvirtConnTestCase(test.TestCase):
conn.volume_driver_method('connect_volume',
v['connection_info'],
v['mount_device'].rpartition("/")[2])
+ self.mox.StubOutWithMock(conn, 'plug_vifs')
+ conn.plug_vifs(mox.IsA(inst_ref), nw_info)
- # Starting test
self.mox.ReplayAll()
- self.assertEqual(conn.pre_live_migration(vol), None)
+ result = conn.pre_live_migration(c, inst_ref, vol, nw_info)
+ self.assertEqual(result, None)
def test_pre_block_migration_works_correctly(self):
"""Confirms pre_block_migration works correctly."""
diff --git a/nova/tests/test_virt_drivers.py b/nova/tests/test_virt_drivers.py
index bc89a24e8..b724c2276 100644
--- a/nova/tests/test_virt_drivers.py
+++ b/nova/tests/test_virt_drivers.py
@@ -456,32 +456,6 @@ class _VirtDriverTestCase(_FakeDriverBackendTestCase):
self.connection.refresh_provider_fw_rules()
@catch_notimplementederror
- def test_compare_cpu(self):
- cpu_info = '''{ "topology": {
- "sockets": 1,
- "cores": 2,
- "threads": 1 },
- "features": [
- "xtpr",
- "tm2",
- "est",
- "vmx",
- "ds_cpl",
- "monitor",
- "pbe",
- "tm",
- "ht",
- "ss",
- "acpi",
- "ds",
- "vme"],
- "arch": "x86_64",
- "model": "Penryn",
- "vendor": "Intel" }'''
-
- self.connection.compare_cpu(cpu_info)
-
- @catch_notimplementederror
def test_ensure_filtering_for_instance(self):
instance_ref = test_utils.get_test_instance()
network_info = test_utils.get_test_network_info()
diff --git a/nova/virt/baremetal/driver.py b/nova/virt/baremetal/driver.py
index 50e1529c1..5ab0fe7d1 100644
--- a/nova/virt/baremetal/driver.py
+++ b/nova/virt/baremetal/driver.py
@@ -716,9 +716,6 @@ class BareMetalDriver(driver.ComputeDriver):
LOG.info(_('Compute_service record updated for %s ') % host)
db.compute_node_update(ctxt, compute_node_ref[0]['id'], dic)
- def compare_cpu(self, cpu_info):
- raise NotImplementedError()
-
def ensure_filtering_rules_for_instance(self, instance_ref,
time=None):
raise NotImplementedError()
diff --git a/nova/virt/driver.py b/nova/virt/driver.py
index e29d2d0eb..6c370ffbd 100644
--- a/nova/virt/driver.py
+++ b/nova/virt/driver.py
@@ -251,20 +251,6 @@ class ComputeDriver(object):
"""Detach the disk attached to the instance"""
raise NotImplementedError()
- def compare_cpu(self, cpu_info):
- """Compares given cpu info against host
-
- Before attempting to migrate a VM to this host,
- compare_cpu is called to ensure that the VM will
- actually run here.
-
- :param cpu_info: (str) JSON structure describing the source CPU.
- :returns: None if migration is acceptable
- :raises: :py:class:`~nova.exception.InvalidCPUInfo` if migration
- is not acceptable.
- """
- raise NotImplementedError()
-
def migrate_disk_and_power_off(self, context, instance, dest,
instance_type, network_info):
"""
@@ -357,27 +343,63 @@ class ComputeDriver(object):
:param host: hostname that compute manager is currently running
"""
- # TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
def live_migration(self, ctxt, instance_ref, dest,
- post_method, recover_method):
- """Spawning live_migration operation for distributing high-load.
+ post_method, recover_method, block_migration=False):
+ """Live migration of an instance to another host.
- :param ctxt: security context
- :param instance_ref:
+ :params ctxt: security context
+ :params instance_ref:
nova.db.sqlalchemy.models.Instance object
instance object that is migrated.
- :param dest: destination host
- :param post_method:
+ :params dest: destination host
+ :params post_method:
post operation method.
expected nova.compute.manager.post_live_migration.
- :param recover_method:
+ :params recover_method:
recovery method when any exception occurs.
expected nova.compute.manager.recover_live_migration.
+ :params block_migration: if true, migrate VM disk.
+ """
+ raise NotImplementedError()
+
+ def check_can_live_migrate_destination(self, ctxt, instance_ref,
+ block_migration=False,
+ disk_over_commit=False):
+ """Check if it is possible to execute live migration.
+
+ This runs checks on the destination host, and then calls
+ back to the source host to check the results.
+
+ :param ctxt: security context
+ :param instance_ref: nova.db.sqlalchemy.models.Instance
+ :param dest: destination host
+ :param block_migration: if true, prepare for block migration
+ :param disk_over_commit: if true, allow disk over commit
+ """
+ raise NotImplementedError()
+ def check_can_live_migrate_destination_cleanup(self, ctxt,
+ dest_check_data):
+ """Do required cleanup on dest host after check_can_live_migrate calls
+
+ :param ctxt: security context
+ :param dest_check_data: result of check_can_live_migrate_destination
+ """
+ raise NotImplementedError()
+
+ def check_can_live_migrate_source(self, ctxt, instance_ref,
+ dest_check_data):
+ """Check if it is possible to execute live migration.
+
+ This checks if the live migration can succeed, based on the
+ results from check_can_live_migrate_destination.
+
+ :param context: security context
+ :param instance_ref: nova.db.sqlalchemy.models.Instance
+ :param dest_check_data: result of check_can_live_migrate_destination
"""
- # TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
def refresh_security_group_rules(self, security_group_id):
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index 85253c0fa..cf143480b 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -259,10 +259,6 @@ class FakeDriver(driver.ComputeDriver):
LOG.info(_('Compute_service record updated for %s ') % host)
db.compute_node_update(ctxt, compute_node_ref[0]['id'], dic)
- def compare_cpu(self, xml):
- """This method is supported only by libvirt."""
- raise NotImplementedError('This method is supported only by libvirt.')
-
def ensure_filtering_rules_for_instance(self, instance_ref, network_info):
"""This method is supported only by libvirt."""
raise NotImplementedError('This method is supported only by libvirt.')
@@ -283,7 +279,8 @@ class FakeDriver(driver.ComputeDriver):
def confirm_migration(self, migration, instance, network_info):
return
- def pre_live_migration(self, block_device_info):
+ def pre_live_migration(self, context, instance_ref, block_device_info,
+ network_info):
"""This method is supported only by libvirt."""
return
diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py
index fc710a854..6a68f5cd7 100644
--- a/nova/virt/libvirt/driver.py
+++ b/nova/virt/libvirt/driver.py
@@ -2202,7 +2202,118 @@ class LibvirtDriver(driver.ComputeDriver):
LOG.info(_('Compute_service record updated for %s ') % host)
db.compute_node_update(ctxt, compute_node_ref[0]['id'], dic)
- def compare_cpu(self, cpu_info):
+ def check_can_live_migrate_destination(self, ctxt, instance_ref,
+ block_migration=False,
+ disk_over_commit=False):
+ """Check if it is possible to execute live migration.
+
+ This runs checks on the destination host, and then calls
+ back to the source host to check the results.
+
+ :param ctxt: security context
+ :param instance_ref: nova.db.sqlalchemy.models.Instance
+ :param dest: destination host
+ :param block_migration: if true, prepare for block migration
+ :param disk_over_commit: if true, allow disk over commit
+ """
+ if block_migration:
+ self._assert_compute_node_has_enough_disk(ctxt,
+ instance_ref,
+ disk_over_commit)
+ # Compare CPU
+ src = instance_ref['host']
+ source_cpu_info = self._get_compute_info(ctxt, src)['cpu_info']
+ self._compare_cpu(source_cpu_info)
+
+ # Create file on storage, to be checked on source host
+ filename = self._create_shared_storage_test_file()
+
+ return {"filename": filename, "block_migration": block_migration}
+
+ def check_can_live_migrate_destination_cleanup(self, ctxt,
+ dest_check_data):
+ """Do required cleanup on dest host after check_can_live_migrate calls
+
+ :param ctxt: security context
+ :param disk_over_commit: if true, allow disk over commit
+ """
+ filename = dest_check_data["filename"]
+ self._cleanup_shared_storage_test_file(filename)
+
+ def check_can_live_migrate_source(self, ctxt, instance_ref,
+ dest_check_data):
+ """Check if it is possible to execute live migration.
+
+ This checks if the live migration can succeed, based on the
+ results from check_can_live_migrate_destination.
+
+ :param context: security context
+ :param instance_ref: nova.db.sqlalchemy.models.Instance
+ :param dest_check_data: result of check_can_live_migrate_destination
+ """
+ # Checking shared storage connectivity
+ # if block migration, instances_paths should not be on shared storage.
+ dest = FLAGS.host
+ filename = dest_check_data["filename"]
+ block_migration = dest_check_data["block_migration"]
+
+ shared = self._check_shared_storage_test_file(filename)
+
+ if block_migration:
+ if shared:
+ reason = _("Block migration can not be used "
+ "with shared storage.")
+ raise exception.InvalidSharedStorage(reason=reason, path=dest)
+
+ elif not shared:
+ reason = _("Live migration can not be used "
+ "without shared storage.")
+ raise exception.InvalidSharedStorage(reason=reason, path=dest)
+
+ def _get_compute_info(self, context, host):
+ """Get compute host's information specified by key"""
+ compute_node_ref = db.service_get_all_compute_by_host(context, host)
+ return compute_node_ref[0]['compute_node'][0]
+
+ def _assert_compute_node_has_enough_disk(self, context, instance_ref,
+ disk_over_commit):
+ """Checks if host has enough disk for block migration."""
+ # Libvirt supports qcow2 disk format,which is usually compressed
+ # on compute nodes.
+ # Real disk image (compressed) may enlarged to "virtual disk size",
+ # that is specified as the maximum disk size.
+ # (See qemu-img -f path-to-disk)
+ # Scheduler recognizes destination host still has enough disk space
+ # if real disk size < available disk size
+ # if disk_over_commit is True,
+ # otherwise virtual disk size < available disk size.
+
+ # Getting total available disk of host
+ dest = FLAGS.host
+ available_gb = self._get_compute_info(context,
+ dest)['disk_available_least']
+ available = available_gb * (1024 ** 3)
+
+ ret = self.get_instance_disk_info(instance_ref['name'])
+ disk_infos = jsonutils.loads(ret)
+
+ necessary = 0
+ if disk_over_commit:
+ for info in disk_infos:
+ necessary += int(info['disk_size'])
+ else:
+ for info in disk_infos:
+ necessary += int(info['virt_disk_size'])
+
+ # Check that available disk > necessary disk
+ if (available - necessary) < 0:
+ instance_uuid = instance_ref['uuid']
+ reason = _("Unable to migrate %(instance_uuid)s to %(dest)s: "
+ "Lack of disk(host:%(available)s "
+ "<= instance:%(necessary)s)")
+ raise exception.MigrationError(reason=reason % locals())
+
+ def _compare_cpu(self, cpu_info):
"""Checks the host cpu is compatible to a cpu given by xml.
"xml" must be a part of libvirt.openReadonly().getCapabilities().
@@ -2214,9 +2325,7 @@ class LibvirtDriver(driver.ComputeDriver):
:returns:
None. if given cpu info is not compatible to this server,
raise exception.
-
"""
-
info = jsonutils.loads(cpu_info)
LOG.info(_('Instance launched has CPU info:\n%s') % cpu_info)
cpu = config.LibvirtConfigCPU()
@@ -2240,8 +2349,33 @@ class LibvirtDriver(driver.ComputeDriver):
raise
if ret <= 0:
+ LOG.error(reason=m % locals())
raise exception.InvalidCPUInfo(reason=m % locals())
+ def _create_shared_storage_test_file(self):
+ """Makes tmpfile under FLAGS.instance_path."""
+ dirpath = FLAGS.instances_path
+ fd, tmp_file = tempfile.mkstemp(dir=dirpath)
+ LOG.debug(_("Creating tmpfile %s to notify to other "
+ "compute nodes that they should mount "
+ "the same storage.") % tmp_file)
+ os.close(fd)
+ return os.path.basename(tmp_file)
+
+ def _check_shared_storage_test_file(self, filename):
+ """Confirms existence of the tmpfile under FLAGS.instances_path.
+ Cannot confirm tmpfile return False."""
+ tmp_file = os.path.join(FLAGS.instances_path, filename)
+ if not os.path.exists(tmp_file):
+ return False
+ else:
+ return True
+
+ def _cleanup_shared_storage_test_file(self, filename):
+ """Removes existence of the tmpfile under FLAGS.instances_path."""
+ tmp_file = os.path.join(FLAGS.instances_path, filename)
+ os.remove(tmp_file)
+
def ensure_filtering_rules_for_instance(self, instance_ref, network_info,
time=None):
"""Setting up filtering rules and waiting for its completion.
@@ -2363,14 +2497,9 @@ class LibvirtDriver(driver.ComputeDriver):
timer.f = wait_for_live_migration
return timer.start(interval=0.5).wait()
- def pre_live_migration(self, block_device_info):
- """Preparation live migration.
-
- :params block_device_info:
- It must be the result of _get_instance_volume_bdms()
- at compute manager.
- """
-
+ def pre_live_migration(self, context, instance_ref, block_device_info,
+ network_info):
+ """Preparation live migration."""
# Establishing connection to volume server.
block_device_mapping = driver.block_device_info_get_mapping(
block_device_info)
@@ -2381,6 +2510,24 @@ class LibvirtDriver(driver.ComputeDriver):
connection_info,
mount_device)
+ # We call plug_vifs before the compute manager calls
+ # ensure_filtering_rules_for_instance, to ensure bridge is set up
+ # Retry operation is necessary because continuously request comes,
+ # concorrent request occurs to iptables, then it complains.
+ max_retry = FLAGS.live_migration_retry_count
+ for cnt in range(max_retry):
+ try:
+ self.plug_vifs(instance_ref, network_info)
+ break
+ except exception.ProcessExecutionError:
+ if cnt == max_retry - 1:
+ raise
+ else:
+ LOG.warn(_("plug_vifs() failed %(cnt)d."
+ "Retry up to %(max_retry)d for %(hostname)s.")
+ % locals())
+ time.sleep(1)
+
def pre_block_migration(self, ctxt, instance_ref, disk_info_json):
"""Preparation block migration.
diff --git a/nova/virt/xenapi/driver.py b/nova/virt/xenapi/driver.py
index 20d32f8ea..c7fad6387 100644
--- a/nova/virt/xenapi/driver.py
+++ b/nova/virt/xenapi/driver.py
@@ -410,10 +410,6 @@ class XenAPIDriver(driver.ComputeDriver):
LOG.info(_('Compute_service record updated for %s ') % host)
db.compute_node_update(ctxt, compute_node_ref[0]['id'], dic)
- def compare_cpu(self, xml):
- """This method is supported only by libvirt."""
- raise NotImplementedError('This method is supported only by libvirt.')
-
def ensure_filtering_rules_for_instance(self, instance_ref, network_info):
"""This method is supported only libvirt."""
# NOTE(salvatore-orlando): it enforces security groups on