Target cell in super conductor operations

This adds a decorator that lets us auto-target cells for methods in
conductor that are supposed to run at super-conductor level and thus
need to look up the instance mapping.

Related to blueprint cells-aware-api

Change-Id: I1c9a16b90774fb76e96b36259327dc0f273ab3c8
This commit is contained in:
Dan Smith 2017-02-23 14:25:41 -08:00
parent 17fbd245cb
commit 30172f6e30
2 changed files with 75 additions and 6 deletions

View File

@ -16,6 +16,7 @@
import contextlib
import copy
import functools
from oslo_config import cfg
from oslo_log import log as logging
@ -53,6 +54,35 @@ LOG = logging.getLogger(__name__)
CONF = cfg.CONF
def targets_cell(fn):
"""Wrap a method and automatically target the instance's cell.
This decorates a method with signature func(self, context, instance, ...)
and automatically targets the context with the instance's cell
mapping. It does this by looking up the InstanceMapping.
"""
@functools.wraps(fn)
def wrapper(self, context, *args, **kwargs):
instance = kwargs.get('instance') or args[0]
try:
im = objects.InstanceMapping.get_by_instance_uuid(
context, instance.uuid)
except exception.InstanceMappingNotFound:
LOG.error(_LE('InstanceMapping not found, unable to target cell'),
instance=instance)
im = None
else:
LOG.debug('Targeting cell %(cell)s for conductor method %(meth)s',
{'cell': im.cell_mapping,
'meth': fn.__name__})
# NOTE(danms): Target our context to the cell for the rest of
# this request, so that none of the subsequent code needs to
# care about it.
nova_context.set_target_cell(context, im.cell_mapping)
return fn(self, context, *args, **kwargs)
return wrapper
class ConductorManager(manager.Manager):
"""Mission: Conduct things.
@ -221,6 +251,7 @@ class ComputeTaskManager(base.Base):
exception.MigrationPreCheckClientException,
exception.LiveMigrationWithOldNovaNotSupported,
exception.UnsupportedPolicyException)
@targets_cell
@wrap_instance_event(prefix='conductor')
def migrate_server(self, context, instance, scheduler_hint, live, rebuild,
flavor, block_migration, disk_over_commit, reservations=None,
@ -494,6 +525,9 @@ class ComputeTaskManager(base.Base):
inst_mapping.save()
return inst_mapping
# NOTE(danms): This is never cell-targeted because it is only used for
# cellsv1 (which does not target cells directly) and n-cpu reschedules
# (which go to the cell conductor and thus are always cell-specific).
def build_instances(self, context, instances, image, filter_properties,
admin_password, injected_files, requested_networks,
security_groups, block_device_mapping=None, legacy_bdm=True):
@ -597,6 +631,7 @@ class ComputeTaskManager(base.Base):
hosts = self.scheduler_client.select_destinations(context, spec_obj)
return hosts
@targets_cell
def unshelve_instance(self, context, instance, request_spec=None):
sys_meta = instance.system_metadata
@ -686,6 +721,7 @@ class ComputeTaskManager(base.Base):
instance.save()
return
@targets_cell
def rebuild_instance(self, context, instance, orig_image_ref, image_ref,
injected_files, new_pass, orig_sys_metadata,
bdms, recreate, on_shared_storage,

View File

@ -331,13 +331,16 @@ class _BaseTaskTestCase(object):
return rebuild_args, compute_rebuild_args
@mock.patch.object(objects.InstanceMapping, 'get_by_instance_uuid')
@mock.patch.object(objects.RequestSpec, 'save')
@mock.patch.object(migrate.MigrationTask, 'execute')
@mock.patch.object(utils, 'get_image_from_system_metadata')
@mock.patch.object(objects.RequestSpec, 'from_components')
def _test_cold_migrate(self, spec_from_components, get_image_from_metadata,
migration_task_execute, spec_save,
migration_task_execute, spec_save, get_im,
clean_shutdown=True):
get_im.return_value.cell_mapping = (
objects.CellMappingList.get_all(self.context)[0])
get_image_from_metadata.return_value = 'image'
inst = fake_instance.fake_db_instance(image_ref='image_ref')
inst_obj = objects.Instance._from_db_object(
@ -1687,13 +1690,20 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
self.assertNotEqual(old_rpcapi,
self.conductor_manager.compute_rpcapi)
def test_migrate_server_fails_with_rebuild(self):
@mock.patch.object(objects.InstanceMapping, 'get_by_instance_uuid')
def test_migrate_server_fails_with_rebuild(self, get_im):
get_im.return_value.cell_mapping = (
objects.CellMappingList.get_all(self.context)[0])
instance = fake_instance.fake_instance_obj(self.context,
vm_state=vm_states.ACTIVE)
self.assertRaises(NotImplementedError, self.conductor.migrate_server,
self.context, instance, None, True, True, None, None, None)
def test_migrate_server_fails_with_flavor(self):
@mock.patch.object(objects.InstanceMapping, 'get_by_instance_uuid')
def test_migrate_server_fails_with_flavor(self, get_im):
get_im.return_value.cell_mapping = (
objects.CellMappingList.get_all(self.context)[0])
flavor = flavors.get_flavor_by_name('m1.tiny')
instance = fake_instance.fake_instance_obj(self.context,
vm_state=vm_states.ACTIVE,
@ -1707,10 +1717,13 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
'uuid': instance['uuid'], },
}
@mock.patch.object(objects.InstanceMapping, 'get_by_instance_uuid')
@mock.patch.object(scheduler_utils, 'set_vm_state_and_notify')
@mock.patch.object(live_migrate.LiveMigrationTask, 'execute')
def _test_migrate_server_deals_with_expected_exceptions(self, ex,
mock_execute, mock_set):
mock_execute, mock_set, get_im):
get_im.return_value.cell_mapping = (
objects.CellMappingList.get_all(self.context)[0])
instance = fake_instance.fake_db_instance(uuid=uuids.instance,
vm_state=vm_states.ACTIVE)
inst_obj = objects.Instance._from_db_object(
@ -1731,7 +1744,10 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
'expected_task_state': task_states.MIGRATING},
ex, self._build_request_spec(inst_obj))
def test_migrate_server_deals_with_invalidcpuinfo_exception(self):
@mock.patch.object(objects.InstanceMapping, 'get_by_instance_uuid')
def test_migrate_server_deals_with_invalidcpuinfo_exception(self, get_im):
get_im.return_value.cell_mapping = (
objects.CellMappingList.get_all(self.context)[0])
instance = fake_instance.fake_db_instance(uuid=uuids.instance,
vm_state=vm_states.ACTIVE)
inst_obj = objects.Instance._from_db_object(
@ -1783,10 +1799,13 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
for ex in exs:
self._test_migrate_server_deals_with_expected_exceptions(ex)
@mock.patch.object(objects.InstanceMapping, 'get_by_instance_uuid')
@mock.patch.object(scheduler_utils, 'set_vm_state_and_notify')
@mock.patch.object(live_migrate.LiveMigrationTask, 'execute')
def test_migrate_server_deals_with_unexpected_exceptions(self,
mock_live_migrate, mock_set_state):
mock_live_migrate, mock_set_state, get_im):
get_im.return_value.cell_mapping = (
objects.CellMappingList.get_all(self.context)[0])
expected_ex = IOError('fake error')
mock_live_migrate.side_effect = expected_ex
instance = fake_instance.fake_db_instance()
@ -2323,6 +2342,20 @@ class ConductorTaskRPCAPITestCase(_BaseTaskTestCase,
self.context, 'live_migrate_instance', **kw)
_test()
@mock.patch.object(objects.InstanceMapping, 'get_by_instance_uuid')
def test_targets_cell_no_instance_mapping(self, mock_im):
@conductor_manager.targets_cell
def test(self, context, instance):
return mock.sentinel.iransofaraway
mock_im.side_effect = exc.InstanceMappingNotFound
ctxt = mock.MagicMock()
inst = mock.MagicMock()
self.assertEqual(mock.sentinel.iransofaraway,
test(None, ctxt, inst))
mock_im.assert_called_once_with(ctxt, inst.uuid)
class ConductorTaskAPITestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
"""Compute task API Tests."""