diff options
-rw-r--r-- | nova/api/openstack/compute/contrib/admin_actions.py | 8 | ||||
-rw-r--r-- | nova/compute/api.py | 2 | ||||
-rw-r--r-- | nova/scheduler/driver.py | 64 | ||||
-rw-r--r-- | nova/scheduler/filter_scheduler.py | 8 | ||||
-rw-r--r-- | nova/tests/integrated/test_api_samples.py | 2 | ||||
-rw-r--r-- | nova/tests/scheduler/test_scheduler.py | 123 |
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') |