summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nova/api/openstack/compute/contrib/admin_actions.py8
-rw-r--r--nova/compute/api.py2
-rw-r--r--nova/scheduler/driver.py64
-rw-r--r--nova/scheduler/filter_scheduler.py8
-rw-r--r--nova/tests/integrated/test_api_samples.py2
-rw-r--r--nova/tests/scheduler/test_scheduler.py123
6 files changed, 180 insertions, 27 deletions
diff --git a/nova/api/openstack/compute/contrib/admin_actions.py b/nova/api/openstack/compute/contrib/admin_actions.py
index dc3ee8fc4..a3f68e828 100644
--- a/nova/api/openstack/compute/contrib/admin_actions.py
+++ b/nova/api/openstack/compute/contrib/admin_actions.py
@@ -285,8 +285,12 @@ class AdminActionsController(wsgi.Controller):
except exception.ComputeServiceUnavailable as ex:
raise exc.HTTPBadRequest(explanation=str(ex))
except Exception:
- msg = _("Live migration of instance %(id)s to host %(host)s"
- " failed") % locals()
+ if host is None:
+ msg = _("Live migration of instance %(id)s to another host"
+ " failed") % locals()
+ else:
+ msg = _("Live migration of instance %(id)s to host %(host)s"
+ " failed") % locals()
LOG.exception(msg)
# Return messages from scheduler
raise exc.HTTPBadRequest(explanation=msg)
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 5e160d2ef..e26b0c5fb 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -2367,7 +2367,7 @@ class API(base.Base):
disk_over_commit, host_name):
"""Migrate a server lively to a new host."""
LOG.debug(_("Going to try to live migrate instance to %s"),
- host_name, instance=instance)
+ host_name or "another host", instance=instance)
instance = self.update(context, instance,
task_state=task_states.MIGRATING,
diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py
index 1a2e8254a..a8531a587 100644
--- a/nova/scheduler/driver.py
+++ b/nova/scheduler/driver.py
@@ -30,6 +30,7 @@ from nova.compute import vm_states
from nova.conductor import api as conductor_api
from nova import db
from nova import exception
+from nova.image import glance
from nova import notifications
from nova.openstack.common import cfg
from nova.openstack.common import importutils
@@ -121,6 +122,7 @@ class Scheduler(object):
CONF.scheduler_host_manager)
self.compute_rpcapi = compute_rpcapi.ComputeAPI()
self.servicegroup_api = servicegroup.API()
+ self.image_service = glance.get_default_image_service()
def update_service_capabilities(self, service_name, host, capabilities):
"""Process a capability update from a service node."""
@@ -182,10 +184,33 @@ class Scheduler(object):
"""
# Check we can do live migration
self._live_migration_src_check(context, instance)
- self._live_migration_dest_check(context, instance, dest)
- self._live_migration_common_check(context, instance, dest)
- migrate_data = self.compute_rpcapi.check_can_live_migrate_destination(
- context, instance, dest, block_migration, disk_over_commit)
+
+ if dest is None:
+ # Let scheduler select a dest host, retry next best until success
+ # or no more valid hosts.
+ ignore_hosts = [instance['host']]
+ while dest is None:
+ dest = self._live_migration_dest_check(context, instance, dest,
+ ignore_hosts)
+ try:
+ self._live_migration_common_check(context, instance, dest)
+ migrate_data = self.compute_rpcapi.\
+ check_can_live_migrate_destination(context, instance,
+ dest,
+ block_migration,
+ disk_over_commit)
+ except exception.Invalid:
+ ignore_hosts.append(dest)
+ dest = None
+ continue
+ else:
+ # Test the given dest host
+ self._live_migration_dest_check(context, instance, dest)
+ self._live_migration_common_check(context, instance, dest)
+ migrate_data = self.compute_rpcapi.\
+ check_can_live_migrate_destination(context, instance, dest,
+ block_migration,
+ disk_over_commit)
# Perform migration
src = instance['host']
@@ -218,14 +243,34 @@ class Scheduler(object):
if not self.servicegroup_api.service_is_up(service):
raise exception.ComputeServiceUnavailable(host=src)
- def _live_migration_dest_check(self, context, instance_ref, dest):
+ def _live_migration_dest_check(self, context, instance_ref, dest,
+ ignore_hosts=None):
"""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 ignore_hosts: hosts that should be avoided as dest host
"""
+ # If dest is not specified, have scheduler pick one.
+ if dest is None:
+ image = self.image_service.show(context, instance_ref['image_ref'])
+ request_spec = {'instance_properties': instance_ref,
+ 'instance_type': instance_ref['instance_type'],
+ 'instance_uuids': [instance_ref['uuid']],
+ 'image': image}
+ filter_properties = {'ignore_hosts': ignore_hosts}
+ return self.select_hosts(context, request_spec,
+ filter_properties)[0]
+
+ # Checking whether The host where instance is running
+ # and dest is not same.
+ src = instance_ref['host']
+ if dest == src:
+ raise exception.UnableToMigrateToSelf(
+ instance_id=instance_ref['uuid'], host=dest)
+
# Checking dest exists and compute node.
try:
dservice_ref = db.service_get_by_compute_host(context, dest)
@@ -236,17 +281,12 @@ class Scheduler(object):
if not self.servicegroup_api.service_is_up(dservice_ref):
raise exception.ComputeServiceUnavailable(host=dest)
- # Checking whether The host where instance is running
- # and dest is not same.
- src = instance_ref['host']
- if dest == src:
- raise exception.UnableToMigrateToSelf(
- instance_id=instance_ref['uuid'], host=dest)
-
# Check memory requirements
self._assert_compute_node_has_enough_memory(context,
instance_ref, dest)
+ return dest
+
def _live_migration_common_check(self, context, instance_ref, dest):
"""Live migration common check routine.
diff --git a/nova/scheduler/filter_scheduler.py b/nova/scheduler/filter_scheduler.py
index ecf909c9b..905b582cd 100644
--- a/nova/scheduler/filter_scheduler.py
+++ b/nova/scheduler/filter_scheduler.py
@@ -139,8 +139,12 @@ class FilterScheduler(driver.Scheduler):
def select_hosts(self, context, request_spec, filter_properties):
"""Selects a filtered set of hosts."""
- return [host.obj.host for host in self._schedule(context, request_spec,
- filter_properties)]
+ instance_uuids = request_spec.get('instance_uuids')
+ hosts = [host.obj.host for host in self._schedule(context,
+ request_spec, filter_properties, instance_uuids)]
+ if not hosts:
+ raise exception.NoValidHost(reason="")
+ return hosts
def _provision_resource(self, context, weighed_host, request_spec,
filter_properties, requested_networks, injected_files,
diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py
index ae34765d9..e179052d6 100644
--- a/nova/tests/integrated/test_api_samples.py
+++ b/nova/tests/integrated/test_api_samples.py
@@ -2136,7 +2136,7 @@ class AdminActionsSamplesJsonTest(ServersSampleBase):
def fake_live_migration_dest_check(self, context, instance_ref, dest):
"""Skip live migration scheduler checks."""
- return
+ return dest
def fake_live_migration_common(self, context, instance_ref, dest):
"""Skip live migration scheduler checks."""
diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py
index 01d3f6a50..5d0228c62 100644
--- a/nova/tests/scheduler/test_scheduler.py
+++ b/nova/tests/scheduler/test_scheduler.py
@@ -31,6 +31,7 @@ from nova.conductor import api as conductor_api
from nova import context
from nova import db
from nova import exception
+from nova.image import glance
from nova.openstack.common import jsonutils
from nova.openstack.common.notifier import api as notifier
from nova.openstack.common import rpc
@@ -39,6 +40,7 @@ from nova.scheduler import manager
from nova import servicegroup
from nova import test
from nova.tests import fake_instance_actions
+from nova.tests.image import fake as fake_image
from nova.tests import matchers
from nova.tests.scheduler import fakes
@@ -337,6 +339,22 @@ class SchedulerTestCase(test.TestCase):
def setUp(self):
super(SchedulerTestCase, self).setUp()
self.stubs.Set(compute_api, 'API', fakes.FakeComputeAPI)
+
+ def fake_show(meh, context, id):
+ if id:
+ return {'id': id, 'min_disk': None, 'min_ram': None,
+ 'name': 'fake_name',
+ 'status': 'active',
+ 'properties': {'kernel_id': 'fake_kernel_id',
+ 'ramdisk_id': 'fake_ramdisk_id',
+ 'something_else': 'meow'}}
+ else:
+ raise exception.ImageNotFound(image_id=id)
+
+ fake_image.stub_out_image_service(self.stubs)
+ self.stubs.Set(fake_image._FakeImageService, 'show', fake_show)
+ self.image_service = glance.get_default_image_service()
+
self.driver = self.driver_cls()
self.context = context.RequestContext('fake_user', 'fake_project')
self.topic = 'fake_topic'
@@ -384,7 +402,8 @@ class SchedulerTestCase(test.TestCase):
'ephemeral_gb': 0,
'vm_state': '',
'task_state': '',
- 'instance_type': {'memory_mb': 1024}}
+ 'instance_type': {'memory_mb': 1024},
+ 'image_ref': 'fake-image-ref'}
def test_live_migration_basic(self):
# Test basic schedule_live_migration functionality.
@@ -402,7 +421,8 @@ class SchedulerTestCase(test.TestCase):
instance = jsonutils.to_primitive(self._live_migration_instance())
self.driver._live_migration_src_check(self.context, instance)
- self.driver._live_migration_dest_check(self.context, instance, dest)
+ self.driver._live_migration_dest_check(self.context, instance,
+ dest).AndReturn(dest)
self.driver._live_migration_common_check(self.context, instance,
dest)
self.driver.compute_rpcapi.check_can_live_migrate_destination(
@@ -595,17 +615,12 @@ class SchedulerTestCase(test.TestCase):
# Confirms exception raises in case dest and src is same host.
self.mox.StubOutWithMock(self.driver, '_live_migration_src_check')
- self.mox.StubOutWithMock(db, 'service_get_by_compute_host')
- self.mox.StubOutWithMock(servicegroup.API, 'service_is_up')
block_migration = False
instance = self._live_migration_instance()
# make dest same as src
dest = instance['host']
self.driver._live_migration_src_check(self.context, instance)
- db.service_get_by_compute_host(self.context,
- dest).AndReturn('fake_service3')
- self.servicegroup_api.service_is_up('fake_service3').AndReturn(True)
self.mox.ReplayAll()
self.assertRaises(exception.UnableToMigrateToSelf,
@@ -668,7 +683,8 @@ class SchedulerTestCase(test.TestCase):
instance = self._live_migration_instance()
self.driver._live_migration_src_check(self.context, instance)
- self.driver._live_migration_dest_check(self.context, instance, dest)
+ self.driver._live_migration_dest_check(self.context, instance,
+ dest).AndReturn(dest)
db.service_get_by_compute_host(self.context, dest).AndReturn(
{'compute_node': [{'hypervisor_type': 'xen',
@@ -700,7 +716,8 @@ class SchedulerTestCase(test.TestCase):
instance = self._live_migration_instance()
self.driver._live_migration_src_check(self.context, instance)
- self.driver._live_migration_dest_check(self.context, instance, dest)
+ self.driver._live_migration_dest_check(self.context, instance,
+ dest).AndReturn(dest)
db.service_get_by_compute_host(self.context, dest).AndReturn(
{'compute_node': [{'hypervisor_type': 'xen',
@@ -716,6 +733,94 @@ class SchedulerTestCase(test.TestCase):
block_migration=block_migration,
disk_over_commit=disk_over_commit)
+ def test_live_migration_dest_check_auto_set_host(self):
+ # Confirm dest is picked by scheduler if not set.
+ self.mox.StubOutWithMock(self.driver, 'select_hosts')
+
+ instance = self._live_migration_instance()
+ request_spec = {'instance_properties': instance,
+ 'instance_type': instance['instance_type'],
+ 'instance_uuids': [instance['uuid']],
+ 'image': self.image_service.show(self.context,
+ instance['image_ref'])
+ }
+ ignore_hosts = [instance['host']]
+ filter_properties = {'ignore_hosts': ignore_hosts}
+
+ self.driver.select_hosts(self.context, request_spec,
+ filter_properties).AndReturn(['fake_host2'])
+
+ self.mox.ReplayAll()
+ result = self.driver._live_migration_dest_check(self.context, instance,
+ None, ignore_hosts)
+ self.assertEqual('fake_host2', result)
+
+ def test_live_migration_auto_set_dest(self):
+ # Confirm scheduler picks target host if none given.
+ self.mox.StubOutWithMock(self.driver, '_live_migration_src_check')
+ self.mox.StubOutWithMock(self.driver, 'select_hosts')
+ self.mox.StubOutWithMock(self.driver, '_live_migration_common_check')
+ self.mox.StubOutWithMock(rpc, 'call')
+ self.mox.StubOutWithMock(self.driver.compute_rpcapi, 'live_migration')
+
+ dest = None
+ block_migration = False
+ disk_over_commit = False
+ instance = self._live_migration_instance()
+ request_spec = {'instance_properties': instance,
+ 'instance_type': instance['instance_type'],
+ 'instance_uuids': [instance['uuid']],
+ 'image': self.image_service.show(self.context,
+ instance['image_ref'])
+ }
+
+ self.driver._live_migration_src_check(self.context, instance)
+
+ # First selected host raises exception.InvalidHypervisorType
+ self.driver.select_hosts(self.context, request_spec,
+ {'ignore_hosts': [instance['host']]}).AndReturn(['fake_host2'])
+ self.driver._live_migration_common_check(self.context, instance,
+ 'fake_host2').AndRaise(exception.InvalidHypervisorType())
+
+ # Second selected host raises exception.InvalidCPUInfo
+ self.driver.select_hosts(self.context, request_spec,
+ {'ignore_hosts': [instance['host'],
+ 'fake_host2']}).AndReturn(['fake_host3'])
+ self.driver._live_migration_common_check(self.context, instance,
+ 'fake_host3')
+ rpc.call(self.context, "compute.fake_host3",
+ {"method": 'check_can_live_migrate_destination',
+ "args": {'instance': instance,
+ 'block_migration': block_migration,
+ 'disk_over_commit': disk_over_commit},
+ "version": compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION},
+ None).AndRaise(exception.InvalidCPUInfo(reason=""))
+
+ # Third selected host pass all checks
+ self.driver.select_hosts(self.context, request_spec,
+ {'ignore_hosts': [instance['host'],
+ 'fake_host2',
+ 'fake_host3']}).AndReturn(['fake_host4'])
+ self.driver._live_migration_common_check(self.context, instance,
+ 'fake_host4')
+ rpc.call(self.context, "compute.fake_host4",
+ {"method": 'check_can_live_migrate_destination',
+ "args": {'instance': instance,
+ 'block_migration': block_migration,
+ 'disk_over_commit': disk_over_commit},
+ "version": compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION},
+ None).AndReturn({})
+ self.driver.compute_rpcapi.live_migration(self.context,
+ host=instance['host'], instance=instance, dest='fake_host4',
+ block_migration=block_migration, migrate_data={})
+
+ self.mox.ReplayAll()
+ result = self.driver.schedule_live_migration(self.context,
+ instance=instance, dest=dest,
+ block_migration=block_migration,
+ disk_over_commit=disk_over_commit)
+ self.assertEqual(result, None)
+
def test_handle_schedule_error_adds_instance_fault(self):
instance = {'uuid': 'fake-uuid'}
self.mox.StubOutWithMock(db, 'instance_update_and_get_original')