From 8a3c7d30914c7cd1f85583316980ece3c33ed3d1 Mon Sep 17 00:00:00 2001 From: Brian Elliott Date: Tue, 27 Nov 2012 16:49:58 +0000 Subject: Make resize and multi-node work properly together Added node support to migrations and update node properly in 'resize_instance'. bug 1081355 Change-Id: I003d34e3f7ed9ce2feda19ee5ce210ed4ba7eaa1 --- nova/compute/manager.py | 16 +++++++++----- nova/compute/resource_tracker.py | 17 +++++++++----- nova/compute/rpcapi.py | 8 ++++--- nova/conductor/manager.py | 2 +- nova/db/api.py | 8 +++---- nova/db/sqlalchemy/api.py | 9 +++++--- nova/scheduler/filter_scheduler.py | 3 ++- nova/tests/compute/test_resource_tracker.py | 11 +++++---- nova/tests/compute/test_rpcapi.py | 3 ++- nova/tests/scheduler/test_filter_scheduler.py | 2 +- nova/tests/test_db_api.py | 32 ++++++++++++++++++--------- 11 files changed, 71 insertions(+), 40 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index a0835d107..7d793ad8c 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -307,7 +307,7 @@ class ComputeVirtAPI(virtapi.VirtAPI): class ComputeManager(manager.SchedulerDependentManager): """Manages the running instances from creation to destruction.""" - RPC_API_VERSION = '2.19' + RPC_API_VERSION = '2.20' def __init__(self, compute_driver=None, *args, **kwargs): """Load configuration options and connect to the hypervisor.""" @@ -1770,7 +1770,7 @@ class ComputeManager(manager.SchedulerDependentManager): QUOTAS.rollback(context, reservations) def _prep_resize(self, context, image, instance, instance_type, - reservations, request_spec, filter_properties): + reservations, request_spec, filter_properties, node): if not filter_properties: filter_properties = {} @@ -1787,7 +1787,7 @@ class ComputeManager(manager.SchedulerDependentManager): raise exception.MigrationError(msg) limits = filter_properties.get('limits', {}) - rt = self._get_resource_tracker(instance.get('node')) + rt = self._get_resource_tracker(node) with rt.resize_claim(context, instance, instance_type, limits=limits) \ as claim: migration_ref = claim.migration @@ -1802,12 +1802,17 @@ class ComputeManager(manager.SchedulerDependentManager): @wrap_instance_fault def prep_resize(self, context, image, instance, instance_type, reservations=None, request_spec=None, - filter_properties=None): + filter_properties=None, node=None): """Initiates the process of moving a running instance to another host. Possibly changes the RAM and disk size in the process. """ + if node is None: + node = self.driver.get_available_nodes()[0] + LOG.debug(_("No node specified, defaulting to %(node)s") % + locals()) + with self._error_out_instance_on_exception(context, instance['uuid'], reservations): compute_utils.notify_usage_exists( @@ -1816,7 +1821,7 @@ class ComputeManager(manager.SchedulerDependentManager): context, instance, "resize.prep.start") try: self._prep_resize(context, image, instance, instance_type, - reservations, request_spec, filter_properties) + reservations, request_spec, filter_properties, node) except Exception: # try to re-schedule the resize elsewhere: self._reschedule_resize_or_reraise(context, image, instance, @@ -1911,6 +1916,7 @@ class ComputeManager(manager.SchedulerDependentManager): instance = self._instance_update(context, instance['uuid'], host=migration['dest_compute'], + node=migration['dest_node'], task_state=task_states.RESIZE_MIGRATED, expected_task_state=task_states. RESIZE_MIGRATING) diff --git a/nova/compute/resource_tracker.py b/nova/compute/resource_tracker.py index ac46f31da..b3e9538bd 100644 --- a/nova/compute/resource_tracker.py +++ b/nova/compute/resource_tracker.py @@ -167,7 +167,9 @@ class ResourceTracker(object): return db.migration_create(context.elevated(), {'instance_uuid': instance['uuid'], 'source_compute': instance['host'], + 'source_node': instance['node'], 'dest_compute': self.host, + 'dest_node': self.nodename, 'dest_host': self.driver.get_host_ip_addr(), 'old_instance_type_id': old_instance_type['id'], 'new_instance_type_id': instance_type['id'], @@ -258,7 +260,8 @@ class ResourceTracker(object): self._update_usage_from_instances(resources, instances) # Grab all in-progress migrations: - migrations = db.migration_get_in_progress_by_host(context, self.host) + migrations = db.migration_get_in_progress_by_host_and_node(context, + self.host, self.nodename) self._update_usage_from_migrations(resources, migrations) @@ -377,15 +380,17 @@ class ResourceTracker(object): uuid = migration['instance_uuid'] LOG.audit(_("Updating from migration %s") % uuid) - incoming = (migration['dest_compute'] == self.host) - outbound = (migration['source_compute'] == self.host) - same_host = (incoming and outbound) + incoming = (migration['dest_compute'] == self.host and + migration['dest_node'] == self.nodename) + outbound = (migration['source_compute'] == self.host and + migration['source_node'] == self.nodename) + same_node = (incoming and outbound) instance = self.tracked_instances.get(uuid, None) itype = None - if same_host: - # same host resize. record usage for whichever instance type the + if same_node: + # same node resize. record usage for whichever instance type the # instance is *not* in: if (instance['instance_type_id'] == migration['old_instance_type_id']): diff --git a/nova/compute/rpcapi.py b/nova/compute/rpcapi.py index 6f69a3cd8..bdb2c4d2d 100644 --- a/nova/compute/rpcapi.py +++ b/nova/compute/rpcapi.py @@ -147,6 +147,7 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy): 2.17 - Add get_backdoor_port() 2.18 - Add bdms to rebuild_instance 2.19 - Add node to run_instance + 2.20 - Add node to prep_resize ''' # @@ -357,16 +358,17 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy): def prep_resize(self, ctxt, image, instance, instance_type, host, reservations=None, request_spec=None, - filter_properties=None): + filter_properties=None, node=None): instance_p = jsonutils.to_primitive(instance) instance_type_p = jsonutils.to_primitive(instance_type) self.cast(ctxt, self.make_msg('prep_resize', instance=instance_p, instance_type=instance_type_p, image=image, reservations=reservations, request_spec=request_spec, - filter_properties=filter_properties), + filter_properties=filter_properties, + node=node), _compute_topic(self.topic, ctxt, host, None), - version='2.10') + version='2.20') def reboot_instance(self, ctxt, instance, block_device_info, network_info, reboot_type): diff --git a/nova/conductor/manager.py b/nova/conductor/manager.py index 6270fb241..4cb6672f4 100644 --- a/nova/conductor/manager.py +++ b/nova/conductor/manager.py @@ -24,7 +24,7 @@ LOG = logging.getLogger(__name__) allowed_updates = ['task_state', 'vm_state', 'expected_task_state', 'power_state', 'access_ip_v4', 'access_ip_v6', - 'launched_at', 'terminated_at', 'host', + 'launched_at', 'terminated_at', 'host', 'node', 'memory_mb', 'vcpus', 'root_gb', 'ephemeral_gb', 'instance_type_id', 'root_device_name', 'host', 'progress', 'vm_mode', 'default_ephemeral_device', diff --git a/nova/db/api.py b/nova/db/api.py index 37acffaf0..d57e612c4 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -404,11 +404,11 @@ def migration_get_unconfirmed_by_dest_compute(context, confirm_window, confirm_window, dest_compute) -def migration_get_in_progress_by_host(context, host): - """Finds all migrations for the given host that are not yet confirmed or - reverted. +def migration_get_in_progress_by_host_and_node(context, host, node): + """Finds all migrations for the given host + node that are not yet + confirmed or reverted. """ - return IMPL.migration_get_in_progress_by_host(context, host) + return IMPL.migration_get_in_progress_by_host_and_node(context, host, node) #################### diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index e1d44b727..c19fc3202 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -3394,11 +3394,14 @@ def migration_get_unconfirmed_by_dest_compute(context, confirm_window, @require_admin_context -def migration_get_in_progress_by_host(context, host, session=None): +def migration_get_in_progress_by_host_and_node(context, host, node, + session=None): return model_query(context, models.Migration, session=session).\ - filter(or_(models.Migration.source_compute == host, - models.Migration.dest_compute == host)).\ + filter(or_(and_(models.Migration.source_compute == host, + models.Migration.source_node == node), + and_(models.Migration.dest_compute == host, + models.Migration.dest_node == node))).\ filter(~models.Migration.status.in_(['confirmed', 'reverted'])).\ options(joinedload('instance')).\ all() diff --git a/nova/scheduler/filter_scheduler.py b/nova/scheduler/filter_scheduler.py index 7e158765a..03fd9cd1a 100644 --- a/nova/scheduler/filter_scheduler.py +++ b/nova/scheduler/filter_scheduler.py @@ -117,7 +117,8 @@ class FilterScheduler(driver.Scheduler): # Forward off to the host self.compute_rpcapi.prep_resize(context, image, instance, instance_type, weighed_host.obj.host, reservations, - request_spec=request_spec, filter_properties=filter_properties) + request_spec=request_spec, filter_properties=filter_properties, + node=weighed_host.obj.nodename) def _provision_resource(self, context, weighed_host, request_spec, filter_properties, requested_networks, injected_files, diff --git a/nova/tests/compute/test_resource_tracker.py b/nova/tests/compute/test_resource_tracker.py index 85d6c3dd6..ac18e9505 100644 --- a/nova/tests/compute/test_resource_tracker.py +++ b/nova/tests/compute/test_resource_tracker.py @@ -321,8 +321,8 @@ class BaseTrackerTestCase(BaseTestCase): self._fake_compute_node_update) self.stubs.Set(db, 'migration_update', self._fake_migration_update) - self.stubs.Set(db, 'migration_get_in_progress_by_host', - self._fake_migration_get_in_progress_by_host) + self.stubs.Set(db, 'migration_get_in_progress_by_host_and_node', + self._fake_migration_get_in_progress_by_host_and_node) self.tracker.update_available_resource(self.context) self.limits = self._limits() @@ -352,7 +352,8 @@ class BaseTrackerTestCase(BaseTestCase): self.compute.update(values) return self.compute - def _fake_migration_get_in_progress_by_host(self, ctxt, host): + def _fake_migration_get_in_progress_by_host_and_node(self, ctxt, host, + node): status = ['confirmed', 'reverted'] migrations = [] @@ -615,7 +616,9 @@ class ResizeClaimTestCase(BaseTrackerTestCase): migration = { 'id': 1, 'source_compute': 'host1', + 'source_node': 'fakenode', 'dest_compute': 'host2', + 'dest_node': 'fakenode', 'dest_host': '127.0.0.1', 'old_instance_type_id': 1, 'new_instance_type_id': 2, @@ -722,7 +725,7 @@ class ResizeClaimTestCase(BaseTrackerTestCase): def test_revert_reserve_source(self): # if a revert has started at the API and audit runs on # the source compute before the instance flips back to source, - # resources should still be help at the source based on the + # resources should still be held at the source based on the # migration: dest = "desthost" dest_tracker = self._tracker(host=dest) diff --git a/nova/tests/compute/test_rpcapi.py b/nova/tests/compute/test_rpcapi.py index d0796a27d..54d8d47c7 100644 --- a/nova/tests/compute/test_rpcapi.py +++ b/nova/tests/compute/test_rpcapi.py @@ -228,7 +228,8 @@ class ComputeRpcAPITestCase(test.TestCase): reservations=list('fake_res'), request_spec='fake_spec', filter_properties={'fakeprop': 'fakeval'}, - version='2.10') + node='node', + version='2.20') def test_reboot_instance(self): self.maxDiff = None diff --git a/nova/tests/scheduler/test_filter_scheduler.py b/nova/tests/scheduler/test_filter_scheduler.py index e9412ba60..d44c79b72 100644 --- a/nova/tests/scheduler/test_filter_scheduler.py +++ b/nova/tests/scheduler/test_filter_scheduler.py @@ -329,7 +329,7 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): [instance['uuid']]).AndReturn(weighed_hosts) sched.compute_rpcapi.prep_resize(self.context, image, instance, instance_type, 'host', reservations, request_spec=request_spec, - filter_properties=filter_properties) + filter_properties=filter_properties, node='node') self.mox.ReplayAll() sched.schedule_prep_resize(self.context, image, request_spec, diff --git a/nova/tests/test_db_api.py b/nova/tests/test_db_api.py index 21e37d609..7f28c9397 100644 --- a/nova/tests/test_db_api.py +++ b/nova/tests/test_db_api.py @@ -1164,19 +1164,20 @@ class MigrationTestCase(test.TestCase): self._create() self._create(status='reverted') self._create(status='confirmed') - self._create(source_compute='host2', dest_compute='host1') + self._create(source_compute='host2', source_node='b', + dest_compute='host1', dest_node='a') self._create(source_compute='host2', dest_compute='host3') self._create(source_compute='host3', dest_compute='host4') def _create(self, status='migrating', source_compute='host1', - dest_compute='host2'): + source_node='a', dest_compute='host2', dest_node='b'): values = {'host': source_compute} instance = db.instance_create(self.ctxt, values) values = {'status': status, 'source_compute': source_compute, - 'dest_compute': dest_compute, - 'instance_uuid': instance['uuid']} + 'source_node': source_node, 'dest_compute': dest_compute, + 'dest_node': dest_node, 'instance_uuid': instance['uuid']} db.migration_create(self.ctxt, values) def _assert_in_progress(self, migrations): @@ -1184,20 +1185,29 @@ class MigrationTestCase(test.TestCase): self.assertNotEqual('confirmed', migration.status) self.assertNotEqual('reverted', migration.status) - def test_in_progress_host1(self): - migrations = db.migration_get_in_progress_by_host(self.ctxt, 'host1') + def test_in_progress_host1_nodea(self): + migrations = db.migration_get_in_progress_by_host_and_node(self.ctxt, + 'host1', 'a') # 2 as source + 1 as dest self.assertEqual(3, len(migrations)) self._assert_in_progress(migrations) - def test_in_progress_host2(self): - migrations = db.migration_get_in_progress_by_host(self.ctxt, 'host2') - # 2 as dest, 2 as source - self.assertEqual(4, len(migrations)) + def test_in_progress_host1_nodeb(self): + migrations = db.migration_get_in_progress_by_host_and_node(self.ctxt, + 'host1', 'b') + # some migrations are to/from host1, but none with a node 'b' + self.assertEqual(0, len(migrations)) + + def test_in_progress_host2_nodeb(self): + migrations = db.migration_get_in_progress_by_host_and_node(self.ctxt, + 'host2', 'b') + # 2 as dest, 1 as source + self.assertEqual(3, len(migrations)) self._assert_in_progress(migrations) def test_instance_join(self): - migrations = db.migration_get_in_progress_by_host(self.ctxt, 'host2') + migrations = db.migration_get_in_progress_by_host_and_node(self.ctxt, + 'host2', 'b') for migration in migrations: instance = migration['instance'] self.assertEqual(migration['instance_uuid'], instance['uuid']) -- cgit