Cold migrate using the RequestSpec object

Cold migrate and resize can now get the Spec object and pass it
down the wire to the conductor so that it could use it for the
move.

Partially-Implements: blueprint check-destination-on-migrations-newton

Co-Authored-By: Pawel Koniszewski <pawel.koniszewski@intel.com>
Change-Id: Ic3968721d257a167f3f946e5387cd227a7eeec6c
This commit is contained in:
Sylvain Bauza 2016-03-29 12:02:07 +02:00
parent 09f2d4d5ec
commit 76dfb4ba9f
10 changed files with 227 additions and 162 deletions

View File

@ -558,7 +558,12 @@ class CellsAPI(object):
def resize_instance(self, ctxt, instance, extra_instance_updates, def resize_instance(self, ctxt, instance, extra_instance_updates,
scheduler_hint, flavor, reservations, scheduler_hint, flavor, reservations,
clean_shutdown=True): clean_shutdown=True,
request_spec=None):
# NOTE(sbauza): Since Cells v1 is quite feature-frozen, we don't want
# to pass down request_spec to the manager and rather keep the
# cell conductor providing a new RequestSpec like the original
# behaviour
flavor_p = jsonutils.to_primitive(flavor) flavor_p = jsonutils.to_primitive(flavor)
version = '1.33' version = '1.33'
msg_args = {'instance': instance, msg_args = {'instance': instance,

View File

@ -2729,12 +2729,26 @@ class API(base.Base):
self._record_action_start(context, instance, self._record_action_start(context, instance,
instance_actions.RESIZE) instance_actions.RESIZE)
# NOTE(sbauza): The migration script we provided in Newton should make
# sure that all our instances are currently migrated to have an
# attached RequestSpec object but let's consider that the operator only
# half migrated all their instances in the meantime.
try:
request_spec = objects.RequestSpec.get_by_instance_uuid(
context, instance.uuid)
request_spec.ignore_hosts = filter_properties['ignore_hosts']
except exception.RequestSpecNotFound:
# Some old instances can still have no RequestSpec object attached
# to them, we need to support the old way
request_spec = None
scheduler_hint = {'filter_properties': filter_properties} scheduler_hint = {'filter_properties': filter_properties}
self.compute_task_api.resize_instance(context, instance, self.compute_task_api.resize_instance(context, instance,
extra_instance_updates, scheduler_hint=scheduler_hint, extra_instance_updates, scheduler_hint=scheduler_hint,
flavor=new_instance_type, flavor=new_instance_type,
reservations=quotas.reservations or [], reservations=quotas.reservations or [],
clean_shutdown=clean_shutdown) clean_shutdown=clean_shutdown,
request_spec=request_spec)
@wrap_check_policy @wrap_check_policy
@check_instance_lock @check_instance_lock

View File

@ -65,14 +65,15 @@ class LocalComputeTaskAPI(object):
def resize_instance(self, context, instance, extra_instance_updates, def resize_instance(self, context, instance, extra_instance_updates,
scheduler_hint, flavor, reservations, scheduler_hint, flavor, reservations,
clean_shutdown=True): clean_shutdown=True, request_spec=None):
# NOTE(comstud): 'extra_instance_updates' is not used here but is # NOTE(comstud): 'extra_instance_updates' is not used here but is
# needed for compatibility with the cells_rpcapi version of this # needed for compatibility with the cells_rpcapi version of this
# method. # method.
self._manager.migrate_server( self._manager.migrate_server(
context, instance, scheduler_hint, live=False, rebuild=False, context, instance, scheduler_hint, live=False, rebuild=False,
flavor=flavor, block_migration=None, disk_over_commit=None, flavor=flavor, block_migration=None, disk_over_commit=None,
reservations=reservations, clean_shutdown=clean_shutdown) reservations=reservations, clean_shutdown=clean_shutdown,
request_spec=request_spec)
def live_migrate_instance(self, context, instance, host_name, def live_migrate_instance(self, context, instance, host_name,
block_migration, disk_over_commit, block_migration, disk_over_commit,
@ -176,14 +177,15 @@ class ComputeTaskAPI(object):
def resize_instance(self, context, instance, extra_instance_updates, def resize_instance(self, context, instance, extra_instance_updates,
scheduler_hint, flavor, reservations, scheduler_hint, flavor, reservations,
clean_shutdown=True): clean_shutdown=True, request_spec=None):
# NOTE(comstud): 'extra_instance_updates' is not used here but is # NOTE(comstud): 'extra_instance_updates' is not used here but is
# needed for compatibility with the cells_rpcapi version of this # needed for compatibility with the cells_rpcapi version of this
# method. # method.
self.conductor_compute_rpcapi.migrate_server( self.conductor_compute_rpcapi.migrate_server(
context, instance, scheduler_hint, live=False, rebuild=False, context, instance, scheduler_hint, live=False, rebuild=False,
flavor=flavor, block_migration=None, disk_over_commit=None, flavor=flavor, block_migration=None, disk_over_commit=None,
reservations=reservations, clean_shutdown=clean_shutdown) reservations=reservations, clean_shutdown=clean_shutdown,
request_spec=request_spec)
def live_migrate_instance(self, context, instance, host_name, def live_migrate_instance(self, context, instance, host_name,
block_migration, disk_over_commit, block_migration, disk_over_commit,

View File

@ -200,20 +200,30 @@ class ComputeTaskManager(base.Base):
instance_uuid): instance_uuid):
self._cold_migrate(context, instance, flavor, self._cold_migrate(context, instance, flavor,
scheduler_hint['filter_properties'], scheduler_hint['filter_properties'],
reservations, clean_shutdown) reservations, clean_shutdown, request_spec)
else: else:
raise NotImplementedError() raise NotImplementedError()
def _cold_migrate(self, context, instance, flavor, filter_properties, def _cold_migrate(self, context, instance, flavor, filter_properties,
reservations, clean_shutdown): reservations, clean_shutdown, request_spec):
image = utils.get_image_from_system_metadata( image = utils.get_image_from_system_metadata(
instance.system_metadata) instance.system_metadata)
request_spec = scheduler_utils.build_request_spec( # NOTE(sbauza): If a reschedule occurs when prep_resize(), then
context, image, [instance], instance_type=flavor) # it only provides filter_properties legacy dict back to the
# conductor with no RequestSpec part of the payload.
if not request_spec:
request_spec = objects.RequestSpec.from_components(
context, instance.uuid, image,
instance.flavor, instance.numa_topology, instance.pci_requests,
filter_properties, None, instance.availability_zone)
task = self._build_cold_migrate_task(context, instance, flavor, task = self._build_cold_migrate_task(context, instance, flavor,
filter_properties, request_spec, request_spec,
reservations, clean_shutdown) reservations, clean_shutdown)
# TODO(sbauza): Provide directly the RequestSpec object once
# _set_vm_state_and_notify() accepts it
legacy_spec = request_spec.to_legacy_request_spec_dict()
try: try:
task.execute() task.execute()
except exception.NoValidHost as ex: except exception.NoValidHost as ex:
@ -223,7 +233,7 @@ class ComputeTaskManager(base.Base):
updates = {'vm_state': vm_state, 'task_state': None} updates = {'vm_state': vm_state, 'task_state': None}
self._set_vm_state_and_notify(context, instance.uuid, self._set_vm_state_and_notify(context, instance.uuid,
'migrate_server', 'migrate_server',
updates, ex, request_spec) updates, ex, legacy_spec)
# if the flavor IDs match, it's migrate; otherwise resize # if the flavor IDs match, it's migrate; otherwise resize
if flavor.id == instance.instance_type_id: if flavor.id == instance.instance_type_id:
@ -239,14 +249,14 @@ class ComputeTaskManager(base.Base):
updates = {'vm_state': vm_state, 'task_state': None} updates = {'vm_state': vm_state, 'task_state': None}
self._set_vm_state_and_notify(context, instance.uuid, self._set_vm_state_and_notify(context, instance.uuid,
'migrate_server', 'migrate_server',
updates, ex, request_spec) updates, ex, legacy_spec)
except Exception as ex: except Exception as ex:
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
updates = {'vm_state': instance.vm_state, updates = {'vm_state': instance.vm_state,
'task_state': None} 'task_state': None}
self._set_vm_state_and_notify(context, instance.uuid, self._set_vm_state_and_notify(context, instance.uuid,
'migrate_server', 'migrate_server',
updates, ex, request_spec) updates, ex, legacy_spec)
def _set_vm_state_and_notify(self, context, instance_uuid, method, updates, def _set_vm_state_and_notify(self, context, instance_uuid, method, updates,
ex, request_spec): ex, request_spec):
@ -351,10 +361,10 @@ class ComputeTaskManager(base.Base):
request_spec) request_spec)
def _build_cold_migrate_task(self, context, instance, flavor, def _build_cold_migrate_task(self, context, instance, flavor,
filter_properties, request_spec, reservations, request_spec, reservations,
clean_shutdown): clean_shutdown):
return migrate.MigrationTask(context, instance, flavor, return migrate.MigrationTask(context, instance, flavor,
filter_properties, request_spec, request_spec,
reservations, clean_shutdown, reservations, clean_shutdown,
self.compute_rpcapi, self.compute_rpcapi,
self.scheduler_client) self.scheduler_client)

View File

@ -10,20 +10,21 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from oslo_serialization import jsonutils
from nova.conductor.tasks import base from nova.conductor.tasks import base
from nova import objects from nova import objects
from nova.scheduler import utils as scheduler_utils from nova.scheduler import utils as scheduler_utils
class MigrationTask(base.TaskBase): class MigrationTask(base.TaskBase):
def __init__(self, context, instance, flavor, filter_properties, def __init__(self, context, instance, flavor,
request_spec, reservations, clean_shutdown, compute_rpcapi, request_spec, reservations, clean_shutdown, compute_rpcapi,
scheduler_client): scheduler_client):
super(MigrationTask, self).__init__(context, instance) super(MigrationTask, self).__init__(context, instance)
self.clean_shutdown = clean_shutdown self.clean_shutdown = clean_shutdown
self.request_spec = request_spec self.request_spec = request_spec
self.reservations = reservations self.reservations = reservations
self.filter_properties = filter_properties
self.flavor = flavor self.flavor = flavor
self.quotas = None self.quotas = None
@ -31,33 +32,52 @@ class MigrationTask(base.TaskBase):
self.scheduler_client = scheduler_client self.scheduler_client = scheduler_client
def _execute(self): def _execute(self):
image = self.request_spec.get('image') image = self.request_spec.image
self.quotas = objects.Quotas.from_reservations(self.context, self.quotas = objects.Quotas.from_reservations(self.context,
self.reservations, self.reservations,
instance=self.instance) instance=self.instance)
scheduler_utils.setup_instance_group(self.context, self.request_spec, # TODO(sbauza): Remove that once prep_resize() accepts a RequestSpec
self.filter_properties) # object in the signature and all the scheduler.utils methods too
scheduler_utils.populate_retry(self.filter_properties, legacy_spec = self.request_spec.to_legacy_request_spec_dict()
legacy_props = self.request_spec.to_legacy_filter_properties_dict()
scheduler_utils.setup_instance_group(self.context, legacy_spec,
legacy_props)
scheduler_utils.populate_retry(legacy_props,
self.instance.uuid) self.instance.uuid)
# TODO(sbauza): Hydrate here the object until we modify the
# scheduler.utils methods to directly use the RequestSpec object # TODO(sbauza): Remove that RequestSpec rehydratation once
spec_obj = objects.RequestSpec.from_primitives( # scheduler.utils methods use directly the NovaObject.
self.context, self.request_spec, self.filter_properties) self.request_spec = objects.RequestSpec.from_components(
self.context, self.instance.uuid, image,
self.instance.flavor, self.instance.numa_topology,
self.instance.pci_requests, legacy_props, None,
self.instance.availability_zone)
# NOTE(sbauza): Force_hosts/nodes needs to be reset
# if we want to make sure that the next destination
# is not forced to be the original host
self.request_spec.reset_forced_destinations()
hosts = self.scheduler_client.select_destinations( hosts = self.scheduler_client.select_destinations(
self.context, spec_obj) self.context, self.request_spec)
host_state = hosts[0] host_state = hosts[0]
scheduler_utils.populate_filter_properties(self.filter_properties, scheduler_utils.populate_filter_properties(legacy_props,
host_state) host_state)
# context is not serializable # context is not serializable
self.filter_properties.pop('context', None) legacy_props.pop('context', None)
(host, node) = (host_state['host'], host_state['nodename']) (host, node) = (host_state['host'], host_state['nodename'])
# FIXME(sbauza): Serialize/Unserialize the legacy dict because of
# oslo.messaging #1529084 to transform datetime values into strings.
# tl;dr: datetimes in dicts are not accepted as correct values by the
# rpc fake driver.
legacy_spec = jsonutils.loads(jsonutils.dumps(legacy_spec))
self.compute_rpcapi.prep_resize( self.compute_rpcapi.prep_resize(
self.context, image, self.instance, self.flavor, host, self.context, legacy_spec['image'], self.instance,
self.reservations, request_spec=self.request_spec, self.flavor, host, self.reservations,
filter_properties=self.filter_properties, node=node, request_spec=legacy_spec, filter_properties=legacy_props,
clean_shutdown=self.clean_shutdown) node=node, clean_shutdown=self.clean_shutdown)
def rollback(self): def rollback(self):
if self.quotas: if self.quotas:

View File

@ -668,6 +668,24 @@ class CellsAPITestCase(test.NoDBTestCase):
self._check_result(call_info, 'resize_instance', self._check_result(call_info, 'resize_instance',
expected_args, version='1.33') expected_args, version='1.33')
def test_resize_instance_not_passing_request_spec(self):
call_info = self._stub_rpc_method('cast', None)
self.cells_rpcapi.resize_instance(self.fake_context,
'fake-instance',
dict(cow='moo'),
'fake-hint',
'fake-flavor',
'fake-reservations',
clean_shutdown=True,
request_spec='fake-spec')
expected_args = {'instance': 'fake-instance',
'flavor': 'fake-flavor',
'extra_instance_updates': dict(cow='moo'),
'clean_shutdown': True}
self._check_result(call_info, 'resize_instance',
expected_args, version='1.33')
def test_live_migrate_instance(self): def test_live_migrate_instance(self):
call_info = self._stub_rpc_method('cast', None) call_info = self._stub_rpc_method('cast', None)

View File

@ -1487,6 +1487,7 @@ class _ComputeAPIUnitTestMixIn(object):
self.mox.StubOutWithMock(fake_inst, 'save') self.mox.StubOutWithMock(fake_inst, 'save')
self.mox.StubOutWithMock(quota.QUOTAS, 'commit') self.mox.StubOutWithMock(quota.QUOTAS, 'commit')
self.mox.StubOutWithMock(self.compute_api, '_record_action_start') self.mox.StubOutWithMock(self.compute_api, '_record_action_start')
self.mox.StubOutWithMock(objects.RequestSpec, 'get_by_instance_uuid')
self.mox.StubOutWithMock(self.compute_api.compute_task_api, self.mox.StubOutWithMock(self.compute_api.compute_task_api,
'resize_instance') 'resize_instance')
@ -1571,6 +1572,10 @@ class _ComputeAPIUnitTestMixIn(object):
self.compute_api._record_action_start(self.context, fake_inst, self.compute_api._record_action_start(self.context, fake_inst,
'migrate') 'migrate')
fake_spec = objects.RequestSpec()
objects.RequestSpec.get_by_instance_uuid(
self.context, fake_inst.uuid).AndReturn(fake_spec)
scheduler_hint = {'filter_properties': filter_properties} scheduler_hint = {'filter_properties': filter_properties}
self.compute_api.compute_task_api.resize_instance( self.compute_api.compute_task_api.resize_instance(
@ -1578,7 +1583,8 @@ class _ComputeAPIUnitTestMixIn(object):
scheduler_hint=scheduler_hint, scheduler_hint=scheduler_hint,
flavor=mox.IsA(objects.Flavor), flavor=mox.IsA(objects.Flavor),
reservations=expected_reservations, reservations=expected_reservations,
clean_shutdown=clean_shutdown) clean_shutdown=clean_shutdown,
request_spec=fake_spec)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -1592,6 +1598,11 @@ class _ComputeAPIUnitTestMixIn(object):
clean_shutdown=clean_shutdown, clean_shutdown=clean_shutdown,
**extra_kwargs) **extra_kwargs)
if allow_same_host:
self.assertEqual([], fake_spec.ignore_hosts)
else:
self.assertEqual([fake_inst['host']], fake_spec.ignore_hosts)
def _test_migrate(self, *args, **kwargs): def _test_migrate(self, *args, **kwargs):
self._test_resize(*args, flavor_id_passed=False, **kwargs) self._test_resize(*args, flavor_id_passed=False, **kwargs)
@ -1690,6 +1701,7 @@ class _ComputeAPIUnitTestMixIn(object):
self.compute_api.resize, self.context, self.compute_api.resize, self.context,
fake_inst, flavor_id='flavor-id') fake_inst, flavor_id='flavor-id')
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
@mock.patch('nova.compute.api.API._record_action_start') @mock.patch('nova.compute.api.API._record_action_start')
@mock.patch('nova.compute.api.API._resize_cells_support') @mock.patch('nova.compute.api.API._resize_cells_support')
@mock.patch('nova.conductor.conductor_api.ComputeTaskAPI.resize_instance') @mock.patch('nova.conductor.conductor_api.ComputeTaskAPI.resize_instance')
@ -1698,7 +1710,8 @@ class _ComputeAPIUnitTestMixIn(object):
get_flavor_by_flavor_id, get_flavor_by_flavor_id,
resize_instance_mock, resize_instance_mock,
cells_support_mock, cells_support_mock,
record_mock): record_mock,
get_by_inst):
params = dict(image_ref='') params = dict(image_ref='')
fake_inst = self._create_instance_obj(params=params) fake_inst = self._create_instance_obj(params=params)

View File

@ -352,6 +352,7 @@ class CellsConductorAPIRPCRedirect(test.NoDBTestCase):
# args since this is verified in compute test code. # args since this is verified in compute test code.
self.assertTrue(self.cells_rpcapi.build_instances.called) self.assertTrue(self.cells_rpcapi.build_instances.called)
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
@mock.patch.object(compute_api.API, '_record_action_start') @mock.patch.object(compute_api.API, '_record_action_start')
@mock.patch.object(compute_api.API, '_resize_cells_support') @mock.patch.object(compute_api.API, '_resize_cells_support')
@mock.patch.object(compute_utils, 'reserve_quota_delta') @mock.patch.object(compute_utils, 'reserve_quota_delta')
@ -361,7 +362,7 @@ class CellsConductorAPIRPCRedirect(test.NoDBTestCase):
@mock.patch.object(compute_api.API, '_check_auto_disk_config') @mock.patch.object(compute_api.API, '_check_auto_disk_config')
@mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid') @mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid')
def test_resize_instance(self, _bdms, _check, _extract, _save, _upsize, def test_resize_instance(self, _bdms, _check, _extract, _save, _upsize,
_reserve, _cells, _record): _reserve, _cells, _record, _spec_get_by_uuid):
flavor = objects.Flavor(**test_flavor.fake_flavor) flavor = objects.Flavor(**test_flavor.fake_flavor)
_extract.return_value = flavor _extract.return_value = flavor
orig_system_metadata = {} orig_system_metadata = {}

View File

@ -15,7 +15,6 @@ import mock
from nova.compute import rpcapi as compute_rpcapi from nova.compute import rpcapi as compute_rpcapi
from nova.conductor.tasks import migrate from nova.conductor.tasks import migrate
from nova import objects from nova import objects
from nova.objects import base as obj_base
from nova.scheduler import client as scheduler_client from nova.scheduler import client as scheduler_client
from nova.scheduler import utils as scheduler_utils from nova.scheduler import utils as scheduler_utils
from nova import test from nova import test
@ -30,16 +29,18 @@ class MigrationTaskTestCase(test.NoDBTestCase):
self.user_id = 'fake' self.user_id = 'fake'
self.project_id = 'fake' self.project_id = 'fake'
self.context = FakeContext(self.user_id, self.project_id) self.context = FakeContext(self.user_id, self.project_id)
inst = fake_instance.fake_db_instance(image_ref='image_ref')
self.instance = objects.Instance._from_db_object(
self.context, objects.Instance(), inst, [])
self.instance.system_metadata = {'image_hw_disk_bus': 'scsi'}
self.flavor = fake_flavor.fake_flavor_obj(self.context) self.flavor = fake_flavor.fake_flavor_obj(self.context)
self.flavor.extra_specs = {'extra_specs': 'fake'} self.flavor.extra_specs = {'extra_specs': 'fake'}
self.request_spec = {'instance_type': inst = fake_instance.fake_db_instance(image_ref='image_ref',
obj_base.obj_to_primitive(self.flavor), instance_type=self.flavor)
'instance_properties': {}, inst_object = objects.Instance(
'image': 'image'} flavor=self.flavor,
numa_topology=None,
pci_requests=None,
system_metadata={'image_hw_disk_bus': 'scsi'})
self.instance = objects.Instance._from_db_object(
self.context, inst_object, inst, [])
self.request_spec = objects.RequestSpec(image=objects.ImageMeta())
self.hosts = [dict(host='host1', nodename=None, limits={})] self.hosts = [dict(host='host1', nodename=None, limits={})]
self.filter_properties = {'limits': {}, 'retry': {'num_attempts': 1, self.filter_properties = {'limits': {}, 'retry': {'num_attempts': 1,
'hosts': [['host1', None]]}} 'hosts': [['host1', None]]}}
@ -48,37 +49,35 @@ class MigrationTaskTestCase(test.NoDBTestCase):
def _generate_task(self): def _generate_task(self):
return migrate.MigrationTask(self.context, self.instance, self.flavor, return migrate.MigrationTask(self.context, self.instance, self.flavor,
self.filter_properties, self.request_spec, self.request_spec, self.reservations,
self.reservations, self.clean_shutdown, self.clean_shutdown,
compute_rpcapi.ComputeAPI(), compute_rpcapi.ComputeAPI(),
scheduler_client.SchedulerClient()) scheduler_client.SchedulerClient())
@mock.patch.object(scheduler_utils, 'build_request_spec') @mock.patch.object(objects.RequestSpec, 'from_components')
@mock.patch.object(scheduler_utils, 'setup_instance_group') @mock.patch.object(scheduler_utils, 'setup_instance_group')
@mock.patch.object(objects.RequestSpec, 'from_primitives')
@mock.patch.object(scheduler_client.SchedulerClient, 'select_destinations') @mock.patch.object(scheduler_client.SchedulerClient, 'select_destinations')
@mock.patch.object(compute_rpcapi.ComputeAPI, 'prep_resize') @mock.patch.object(compute_rpcapi.ComputeAPI, 'prep_resize')
@mock.patch.object(objects.Quotas, 'from_reservations') @mock.patch.object(objects.Quotas, 'from_reservations')
def test_execute(self, quotas_mock, prep_resize_mock, def test_execute(self, quotas_mock, prep_resize_mock,
sel_dest_mock, spec_fp_mock, sig_mock, brs_mock): sel_dest_mock, sig_mock, request_spec_from_components):
brs_mock.return_value = self.request_spec
fake_spec = objects.RequestSpec()
spec_fp_mock.return_value = fake_spec
sel_dest_mock.return_value = self.hosts sel_dest_mock.return_value = self.hosts
task = self._generate_task() task = self._generate_task()
request_spec_from_components.return_value = self.request_spec
legacy_request_spec = self.request_spec.to_legacy_request_spec_dict()
task.execute() task.execute()
quotas_mock.assert_called_once_with(self.context, self.reservations, quotas_mock.assert_called_once_with(self.context, self.reservations,
instance=self.instance) instance=self.instance)
sig_mock.assert_called_once_with(self.context, self.request_spec, sig_mock.assert_called_once_with(self.context, legacy_request_spec,
self.filter_properties) self.filter_properties)
task.scheduler_client.select_destinations.assert_called_once_with( task.scheduler_client.select_destinations.assert_called_once_with(
self.context, fake_spec) self.context, self.request_spec)
prep_resize_mock.assert_called_once_with( prep_resize_mock.assert_called_once_with(
self.context, 'image', self.instance, self.flavor, self.context, legacy_request_spec['image'], self.instance,
self.hosts[0]['host'], self.reservations, self.flavor, self.hosts[0]['host'], self.reservations,
request_spec=self.request_spec, request_spec=legacy_request_spec,
filter_properties=self.filter_properties, filter_properties=self.filter_properties,
node=self.hosts[0]['nodename'], clean_shutdown=self.clean_shutdown) node=self.hosts[0]['nodename'], clean_shutdown=self.clean_shutdown)
self.assertFalse(quotas_mock.return_value.rollback.called) self.assertFalse(quotas_mock.return_value.rollback.called)

View File

@ -355,33 +355,22 @@ class _BaseTaskTestCase(object):
self.assertEqual('destination', migration.dest_compute) self.assertEqual('destination', migration.dest_compute)
self.assertEqual(inst_obj.host, migration.source_compute) self.assertEqual(inst_obj.host, migration.source_compute)
def _test_cold_migrate(self, clean_shutdown=True): @mock.patch.object(migrate.MigrationTask, 'execute')
self.mox.StubOutWithMock(utils, 'get_image_from_system_metadata') @mock.patch.object(utils, 'get_image_from_system_metadata')
self.mox.StubOutWithMock(scheduler_utils, 'build_request_spec') @mock.patch.object(objects.RequestSpec, 'from_components')
self.mox.StubOutWithMock(migrate.MigrationTask, 'execute') def _test_cold_migrate(self, spec_from_components, get_image_from_metadata,
migration_task_execute, clean_shutdown=True):
get_image_from_metadata.return_value = 'image'
inst = fake_instance.fake_db_instance(image_ref='image_ref') inst = fake_instance.fake_db_instance(image_ref='image_ref')
inst_obj = objects.Instance._from_db_object( inst_obj = objects.Instance._from_db_object(
self.context, objects.Instance(), inst, []) self.context, objects.Instance(), inst, [])
inst_obj.system_metadata = {'image_hw_disk_bus': 'scsi'} inst_obj.system_metadata = {'image_hw_disk_bus': 'scsi'}
flavor = flavors.get_default_flavor() flavor = flavors.get_default_flavor()
flavor.extra_specs = {'extra_specs': 'fake'} flavor.extra_specs = {'extra_specs': 'fake'}
filter_properties = {'limits': {}, inst_obj.flavor = flavor
'retry': {'num_attempts': 1,
'hosts': [['host1', None]]}}
request_spec = {'instance_type': obj_base.obj_to_primitive(flavor),
'instance_properties': {}}
utils.get_image_from_system_metadata(
inst_obj.system_metadata).AndReturn('image')
scheduler_utils.build_request_spec( fake_spec = fake_request_spec.fake_spec_obj()
self.context, 'image', spec_from_components.return_value = fake_spec
[mox.IsA(objects.Instance)],
instance_type=mox.IsA(objects.Flavor)).AndReturn(request_spec)
task = self.conductor_manager._build_cold_migrate_task(
self.context, inst_obj, flavor, filter_properties,
request_spec, [], clean_shutdown=clean_shutdown)
task.execute()
self.mox.ReplayAll()
scheduler_hint = {'filter_properties': {}} scheduler_hint = {'filter_properties': {}}
@ -398,6 +387,10 @@ class _BaseTaskTestCase(object):
False, False, flavor, None, None, [], False, False, flavor, None, None, [],
clean_shutdown) clean_shutdown)
get_image_from_metadata.assert_called_once_with(
inst_obj.system_metadata)
migration_task_execute.assert_called_once_with()
def test_cold_migrate(self): def test_cold_migrate(self):
self._test_cold_migrate() self._test_cold_migrate()
@ -1355,9 +1348,7 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
self.conductor._set_vm_state_and_notify( self.conductor._set_vm_state_and_notify(
self.context, 1, 'method', 'updates', 'ex', 'request_spec') self.context, 1, 'method', 'updates', 'ex', 'request_spec')
@mock.patch.object(scheduler_utils, 'build_request_spec')
@mock.patch.object(scheduler_utils, 'setup_instance_group') @mock.patch.object(scheduler_utils, 'setup_instance_group')
@mock.patch.object(objects.RequestSpec, 'from_primitives')
@mock.patch.object(utils, 'get_image_from_system_metadata') @mock.patch.object(utils, 'get_image_from_system_metadata')
@mock.patch.object(objects.Quotas, 'from_reservations') @mock.patch.object(objects.Quotas, 'from_reservations')
@mock.patch.object(scheduler_client.SchedulerClient, 'select_destinations') @mock.patch.object(scheduler_client.SchedulerClient, 'select_destinations')
@ -1366,7 +1357,7 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
@mock.patch.object(migrate.MigrationTask, 'rollback') @mock.patch.object(migrate.MigrationTask, 'rollback')
def test_cold_migrate_no_valid_host_back_in_active_state( def test_cold_migrate_no_valid_host_back_in_active_state(
self, rollback_mock, notify_mock, select_dest_mock, quotas_mock, self, rollback_mock, notify_mock, select_dest_mock, quotas_mock,
metadata_mock, spec_fp_mock, sig_mock, brs_mock): metadata_mock, sig_mock):
flavor = flavors.get_flavor_by_name('m1.tiny') flavor = flavors.get_flavor_by_name('m1.tiny')
inst_obj = objects.Instance( inst_obj = objects.Instance(
image_ref='fake-image_ref', image_ref='fake-image_ref',
@ -1374,41 +1365,42 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
vm_state=vm_states.ACTIVE, vm_state=vm_states.ACTIVE,
system_metadata={}, system_metadata={},
uuid=uuids.instance, uuid=uuids.instance,
user_id=fakes.FAKE_USER_ID) user_id=fakes.FAKE_USER_ID,
request_spec = dict(instance_type=dict(extra_specs=dict()), flavor=flavor,
instance_properties=dict()) availability_zone=None,
filter_props = dict(context=None) pci_requests=None,
numa_topology=None)
resvs = 'fake-resvs' resvs = 'fake-resvs'
image = 'fake-image' image = 'fake-image'
fake_spec = objects.RequestSpec() fake_spec = objects.RequestSpec(image=objects.ImageMeta())
legacy_request_spec = fake_spec.to_legacy_request_spec_dict()
metadata_mock.return_value = image metadata_mock.return_value = image
brs_mock.return_value = request_spec
spec_fp_mock.return_value = fake_spec
exc_info = exc.NoValidHost(reason="") exc_info = exc.NoValidHost(reason="")
select_dest_mock.side_effect = exc_info select_dest_mock.side_effect = exc_info
updates = {'vm_state': vm_states.ACTIVE, updates = {'vm_state': vm_states.ACTIVE,
'task_state': None} 'task_state': None}
# Filter properties are populated during code execution
legacy_filter_props = {'retry': {'num_attempts': 1,
'hosts': []}}
self.assertRaises(exc.NoValidHost, self.assertRaises(exc.NoValidHost,
self.conductor._cold_migrate, self.conductor._cold_migrate,
self.context, inst_obj, self.context, inst_obj,
flavor, filter_props, [resvs], flavor, {}, [resvs],
clean_shutdown=True) True, fake_spec)
metadata_mock.assert_called_with({}) metadata_mock.assert_called_with({})
brs_mock.assert_called_once_with(self.context, image,
[inst_obj],
instance_type=flavor)
quotas_mock.assert_called_once_with(self.context, [resvs], quotas_mock.assert_called_once_with(self.context, [resvs],
instance=inst_obj) instance=inst_obj)
sig_mock.assert_called_once_with(self.context, request_spec, sig_mock.assert_called_once_with(self.context, legacy_request_spec,
filter_props) legacy_filter_props)
notify_mock.assert_called_once_with(self.context, inst_obj.uuid, notify_mock.assert_called_once_with(self.context, inst_obj.uuid,
'migrate_server', updates, 'migrate_server', updates,
exc_info, request_spec) exc_info, legacy_request_spec)
rollback_mock.assert_called_once_with() rollback_mock.assert_called_once_with()
@mock.patch.object(scheduler_utils, 'build_request_spec')
@mock.patch.object(scheduler_utils, 'setup_instance_group') @mock.patch.object(scheduler_utils, 'setup_instance_group')
@mock.patch.object(objects.RequestSpec, 'from_primitives') @mock.patch.object(objects.RequestSpec, 'from_components')
@mock.patch.object(utils, 'get_image_from_system_metadata') @mock.patch.object(utils, 'get_image_from_system_metadata')
@mock.patch.object(objects.Quotas, 'from_reservations') @mock.patch.object(objects.Quotas, 'from_reservations')
@mock.patch.object(scheduler_client.SchedulerClient, 'select_destinations') @mock.patch.object(scheduler_client.SchedulerClient, 'select_destinations')
@ -1417,7 +1409,7 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
@mock.patch.object(migrate.MigrationTask, 'rollback') @mock.patch.object(migrate.MigrationTask, 'rollback')
def test_cold_migrate_no_valid_host_back_in_stopped_state( def test_cold_migrate_no_valid_host_back_in_stopped_state(
self, rollback_mock, notify_mock, select_dest_mock, quotas_mock, self, rollback_mock, notify_mock, select_dest_mock, quotas_mock,
metadata_mock, spec_fp_mock, sig_mock, brs_mock): metadata_mock, spec_fc_mock, sig_mock):
flavor = flavors.get_flavor_by_name('m1.tiny') flavor = flavors.get_flavor_by_name('m1.tiny')
inst_obj = objects.Instance( inst_obj = objects.Instance(
image_ref='fake-image_ref', image_ref='fake-image_ref',
@ -1425,18 +1417,23 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
instance_type_id=flavor['id'], instance_type_id=flavor['id'],
system_metadata={}, system_metadata={},
uuid=uuids.instance, uuid=uuids.instance,
user_id=fakes.FAKE_USER_ID) user_id=fakes.FAKE_USER_ID,
flavor=flavor,
numa_topology=None,
pci_requests=None,
availability_zone=None)
image = 'fake-image' image = 'fake-image'
request_spec = dict(instance_type=dict(extra_specs=dict()),
instance_properties=dict(),
image=image)
filter_props = dict(context=None)
resvs = 'fake-resvs' resvs = 'fake-resvs'
fake_spec = objects.RequestSpec()
fake_spec = objects.RequestSpec(image=objects.ImageMeta())
spec_fc_mock.return_value = fake_spec
legacy_request_spec = fake_spec.to_legacy_request_spec_dict()
# Filter properties are populated during code execution
legacy_filter_props = {'retry': {'num_attempts': 1,
'hosts': []}}
metadata_mock.return_value = image metadata_mock.return_value = image
brs_mock.return_value = request_spec
spec_fp_mock.return_value = fake_spec
exc_info = exc.NoValidHost(reason="") exc_info = exc.NoValidHost(reason="")
select_dest_mock.side_effect = exc_info select_dest_mock.side_effect = exc_info
updates = {'vm_state': vm_states.STOPPED, updates = {'vm_state': vm_states.STOPPED,
@ -1444,19 +1441,16 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
self.assertRaises(exc.NoValidHost, self.assertRaises(exc.NoValidHost,
self.conductor._cold_migrate, self.conductor._cold_migrate,
self.context, inst_obj, self.context, inst_obj,
flavor, filter_props, [resvs], flavor, {}, [resvs],
clean_shutdown=True) True, fake_spec)
metadata_mock.assert_called_with({}) metadata_mock.assert_called_with({})
brs_mock.assert_called_once_with(self.context, image,
[inst_obj],
instance_type=flavor)
quotas_mock.assert_called_once_with(self.context, [resvs], quotas_mock.assert_called_once_with(self.context, [resvs],
instance=inst_obj) instance=inst_obj)
sig_mock.assert_called_once_with(self.context, request_spec, sig_mock.assert_called_once_with(self.context, legacy_request_spec,
filter_props) legacy_filter_props)
notify_mock.assert_called_once_with(self.context, inst_obj.uuid, notify_mock.assert_called_once_with(self.context, inst_obj.uuid,
'migrate_server', updates, 'migrate_server', updates,
exc_info, request_spec) exc_info, legacy_request_spec)
rollback_mock.assert_called_once_with() rollback_mock.assert_called_once_with()
def test_cold_migrate_no_valid_host_error_msg(self): def test_cold_migrate_no_valid_host_error_msg(self):
@ -1468,32 +1462,27 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
system_metadata={}, system_metadata={},
uuid=uuids.instance, uuid=uuids.instance,
user_id=fakes.FAKE_USER_ID) user_id=fakes.FAKE_USER_ID)
request_spec = dict(instance_type=dict(extra_specs=dict()), fake_spec = fake_request_spec.fake_spec_obj()
instance_properties=dict())
filter_props = dict(context=None)
resvs = 'fake-resvs' resvs = 'fake-resvs'
image = 'fake-image' image = 'fake-image'
with test.nested( with test.nested(
mock.patch.object(utils, 'get_image_from_system_metadata', mock.patch.object(utils, 'get_image_from_system_metadata',
return_value=image), return_value=image),
mock.patch.object(scheduler_utils, 'build_request_spec',
return_value=request_spec),
mock.patch.object(self.conductor, '_set_vm_state_and_notify'), mock.patch.object(self.conductor, '_set_vm_state_and_notify'),
mock.patch.object(migrate.MigrationTask, mock.patch.object(migrate.MigrationTask,
'execute', 'execute',
side_effect=exc.NoValidHost(reason="")), side_effect=exc.NoValidHost(reason="")),
mock.patch.object(migrate.MigrationTask, 'rollback') mock.patch.object(migrate.MigrationTask, 'rollback')
) as (image_mock, brs_mock, set_vm_mock, task_execute_mock, ) as (image_mock, set_vm_mock, task_execute_mock,
task_rollback_mock): task_rollback_mock):
nvh = self.assertRaises(exc.NoValidHost, nvh = self.assertRaises(exc.NoValidHost,
self.conductor._cold_migrate, self.context, self.conductor._cold_migrate, self.context,
inst_obj, flavor, filter_props, [resvs], inst_obj, flavor, {}, [resvs],
clean_shutdown=True) True, fake_spec)
self.assertIn('cold migrate', nvh.message) self.assertIn('cold migrate', nvh.message)
@mock.patch.object(utils, 'get_image_from_system_metadata') @mock.patch.object(utils, 'get_image_from_system_metadata')
@mock.patch('nova.scheduler.utils.build_request_spec')
@mock.patch.object(migrate.MigrationTask, 'execute') @mock.patch.object(migrate.MigrationTask, 'execute')
@mock.patch.object(migrate.MigrationTask, 'rollback') @mock.patch.object(migrate.MigrationTask, 'rollback')
@mock.patch.object(conductor_manager.ComputeTaskManager, @mock.patch.object(conductor_manager.ComputeTaskManager,
@ -1502,7 +1491,6 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
set_vm_mock, set_vm_mock,
task_rollback_mock, task_rollback_mock,
task_exec_mock, task_exec_mock,
brs_mock,
image_mock): image_mock):
flavor = flavors.get_flavor_by_name('m1.tiny') flavor = flavors.get_flavor_by_name('m1.tiny')
inst_obj = objects.Instance( inst_obj = objects.Instance(
@ -1512,30 +1500,26 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
system_metadata={}, system_metadata={},
uuid=uuids.instance, uuid=uuids.instance,
user_id=fakes.FAKE_USER_ID) user_id=fakes.FAKE_USER_ID)
request_spec = dict(instance_type=dict(extra_specs=dict()),
instance_properties=dict())
filter_props = dict(context=None)
resvs = 'fake-resvs' resvs = 'fake-resvs'
image = 'fake-image' image = 'fake-image'
exception = exc.UnsupportedPolicyException(reason='') exception = exc.UnsupportedPolicyException(reason='')
fake_spec = fake_request_spec.fake_spec_obj()
legacy_request_spec = fake_spec.to_legacy_request_spec_dict()
image_mock.return_value = image image_mock.return_value = image
brs_mock.return_value = request_spec
task_exec_mock.side_effect = exception task_exec_mock.side_effect = exception
self.assertRaises(exc.UnsupportedPolicyException, self.assertRaises(exc.UnsupportedPolicyException,
self.conductor._cold_migrate, self.context, self.conductor._cold_migrate, self.context,
inst_obj, flavor, filter_props, [resvs], inst_obj, flavor, {}, [resvs], True, fake_spec)
clean_shutdown=True)
updates = {'vm_state': vm_states.STOPPED, 'task_state': None} updates = {'vm_state': vm_states.STOPPED, 'task_state': None}
set_vm_mock.assert_called_once_with(self.context, inst_obj.uuid, set_vm_mock.assert_called_once_with(self.context, inst_obj.uuid,
'migrate_server', updates, 'migrate_server', updates,
exception, request_spec) exception, legacy_request_spec)
@mock.patch.object(scheduler_utils, 'build_request_spec')
@mock.patch.object(scheduler_utils, 'setup_instance_group') @mock.patch.object(scheduler_utils, 'setup_instance_group')
@mock.patch.object(objects.RequestSpec, 'from_primitives') @mock.patch.object(objects.RequestSpec, 'from_components')
@mock.patch.object(utils, 'get_image_from_system_metadata') @mock.patch.object(utils, 'get_image_from_system_metadata')
@mock.patch.object(objects.Quotas, 'from_reservations') @mock.patch.object(objects.Quotas, 'from_reservations')
@mock.patch.object(scheduler_client.SchedulerClient, 'select_destinations') @mock.patch.object(scheduler_client.SchedulerClient, 'select_destinations')
@ -1545,8 +1529,8 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
@mock.patch.object(compute_rpcapi.ComputeAPI, 'prep_resize') @mock.patch.object(compute_rpcapi.ComputeAPI, 'prep_resize')
def test_cold_migrate_exception_host_in_error_state_and_raise( def test_cold_migrate_exception_host_in_error_state_and_raise(
self, prep_resize_mock, rollback_mock, notify_mock, self, prep_resize_mock, rollback_mock, notify_mock,
select_dest_mock, quotas_mock, metadata_mock, spec_fp_mock, select_dest_mock, quotas_mock, metadata_mock, spec_fc_mock,
sig_mock, brs_mock): sig_mock):
flavor = flavors.get_flavor_by_name('m1.tiny') flavor = flavors.get_flavor_by_name('m1.tiny')
inst_obj = objects.Instance( inst_obj = objects.Instance(
image_ref='fake-image_ref', image_ref='fake-image_ref',
@ -1554,19 +1538,19 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
instance_type_id=flavor['id'], instance_type_id=flavor['id'],
system_metadata={}, system_metadata={},
uuid=uuids.instance, uuid=uuids.instance,
user_id=fakes.FAKE_USER_ID) user_id=fakes.FAKE_USER_ID,
flavor=flavor,
availability_zone=None,
pci_requests=None,
numa_topology=None)
image = 'fake-image' image = 'fake-image'
request_spec = dict(instance_type=dict(),
instance_properties=dict(),
image=image)
filter_props = dict(context=None)
resvs = 'fake-resvs' resvs = 'fake-resvs'
fake_spec = objects.RequestSpec() fake_spec = objects.RequestSpec(image=objects.ImageMeta())
legacy_request_spec = fake_spec.to_legacy_request_spec_dict()
spec_fc_mock.return_value = fake_spec
hosts = [dict(host='host1', nodename=None, limits={})] hosts = [dict(host='host1', nodename=None, limits={})]
metadata_mock.return_value = image metadata_mock.return_value = image
brs_mock.return_value = request_spec
spec_fp_mock.return_value = fake_spec
exc_info = test.TestingException('something happened') exc_info = test.TestingException('something happened')
select_dest_mock.return_value = hosts select_dest_mock.return_value = hosts
@ -1576,28 +1560,29 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
self.assertRaises(test.TestingException, self.assertRaises(test.TestingException,
self.conductor._cold_migrate, self.conductor._cold_migrate,
self.context, inst_obj, flavor, self.context, inst_obj, flavor,
filter_props, [resvs], {}, [resvs], True, fake_spec)
clean_shutdown=True)
# Filter properties are populated during code execution
legacy_filter_props = {'retry': {'num_attempts': 1,
'hosts': [['host1', None]]},
'limits': {}}
metadata_mock.assert_called_with({}) metadata_mock.assert_called_with({})
brs_mock.assert_called_once_with(self.context, image,
[inst_obj],
instance_type=flavor)
quotas_mock.assert_called_once_with(self.context, [resvs], quotas_mock.assert_called_once_with(self.context, [resvs],
instance=inst_obj) instance=inst_obj)
sig_mock.assert_called_once_with(self.context, request_spec, sig_mock.assert_called_once_with(self.context, legacy_request_spec,
filter_props) legacy_filter_props)
select_dest_mock.assert_called_once_with( select_dest_mock.assert_called_once_with(
self.context, fake_spec) self.context, fake_spec)
prep_resize_mock.assert_called_once_with( prep_resize_mock.assert_called_once_with(
self.context, image, inst_obj, flavor, self.context, legacy_request_spec['image'],
hosts[0]['host'], [resvs], inst_obj, flavor, hosts[0]['host'], [resvs],
request_spec=request_spec, request_spec=legacy_request_spec,
filter_properties=filter_props, filter_properties=legacy_filter_props,
node=hosts[0]['nodename'], clean_shutdown=True) node=hosts[0]['nodename'], clean_shutdown=True)
notify_mock.assert_called_once_with(self.context, inst_obj.uuid, notify_mock.assert_called_once_with(self.context, inst_obj.uuid,
'migrate_server', updates, 'migrate_server', updates,
exc_info, request_spec) exc_info, legacy_request_spec)
rollback_mock.assert_called_once_with() rollback_mock.assert_called_once_with()
def test_resize_no_valid_host_error_msg(self): def test_resize_no_valid_host_error_msg(self):
@ -1611,9 +1596,7 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
uuid=uuids.instance, uuid=uuids.instance,
user_id=fakes.FAKE_USER_ID) user_id=fakes.FAKE_USER_ID)
request_spec = dict(instance_type=dict(extra_specs=dict()), fake_spec = fake_request_spec.fake_spec_obj()
instance_properties=dict())
filter_props = dict(context=None)
resvs = 'fake-resvs' resvs = 'fake-resvs'
image = 'fake-image' image = 'fake-image'
@ -1621,7 +1604,7 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
mock.patch.object(utils, 'get_image_from_system_metadata', mock.patch.object(utils, 'get_image_from_system_metadata',
return_value=image), return_value=image),
mock.patch.object(scheduler_utils, 'build_request_spec', mock.patch.object(scheduler_utils, 'build_request_spec',
return_value=request_spec), return_value=fake_spec),
mock.patch.object(self.conductor, '_set_vm_state_and_notify'), mock.patch.object(self.conductor, '_set_vm_state_and_notify'),
mock.patch.object(migrate.MigrationTask, mock.patch.object(migrate.MigrationTask,
'execute', 'execute',
@ -1631,8 +1614,8 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
task_rb_mock): task_rb_mock):
nvh = self.assertRaises(exc.NoValidHost, nvh = self.assertRaises(exc.NoValidHost,
self.conductor._cold_migrate, self.context, self.conductor._cold_migrate, self.context,
inst_obj, flavor_new, filter_props, inst_obj, flavor_new, {},
[resvs], clean_shutdown=True) [resvs], True, fake_spec)
self.assertIn('resize', nvh.message) self.assertIn('resize', nvh.message)
def test_build_instances_instance_not_found(self): def test_build_instances_instance_not_found(self):