From 1356ef5b571f80d9fc9a0284034e853cb9c97233 Mon Sep 17 00:00:00 2001 From: Sean Mooney Date: Thu, 26 Mar 2020 23:01:35 +0000 Subject: [PATCH] Cyborg evacuate support This change extends the conductor manager to append the cyborg resource request to the request spec when performing an evacuate. This change passes the ARQs to spawn during rebuild and evacuate. On evacuate the existing ARQs will be deleted and new ARQs will be created and bound, during rebuild the existing ARQs are reused. This change extends the rebuild_instance compute rpcapi function to carry the arq_uuids. This eliminates the need to lookup the uuids associated with the arqs assinged to the instance by quering cyborg. Co-Authored-By: Wenping Song Co-Authored-By: Brin Zhang Implements: blueprint cyborg-rebuild-and-evacuate Change-Id: I147bf4d95e6d86ff1f967a8ce37260730f21d236 --- api-guide/source/accelerator-support.rst | 13 +- nova/accelerator/cyborg.py | 9 ++ nova/compute/api.py | 43 ++++--- nova/compute/manager.py | 63 +++++++--- nova/compute/rpcapi.py | 27 +++-- nova/conductor/manager.py | 73 ++++++++--- nova/objects/service.py | 5 +- nova/tests/functional/integrated_helpers.py | 7 ++ nova/tests/functional/test_servers.py | 69 ++++++++--- nova/tests/unit/accelerator/test_cyborg.py | 23 ++++ nova/tests/unit/compute/test_api.py | 38 +++++- nova/tests/unit/compute/test_compute.py | 128 ++++++++------------ nova/tests/unit/compute/test_compute_mgr.py | 42 ++++--- nova/tests/unit/compute/test_rpcapi.py | 55 ++++++++- nova/tests/unit/conductor/test_conductor.py | 3 + nova/virt/driver.py | 3 +- nova/virt/ironic/driver.py | 4 +- 17 files changed, 414 insertions(+), 191 deletions(-) diff --git a/api-guide/source/accelerator-support.rst b/api-guide/source/accelerator-support.rst index cf9f185273d8..a33b3bdd664a 100644 --- a/api-guide/source/accelerator-support.rst +++ b/api-guide/source/accelerator-support.rst @@ -29,9 +29,8 @@ appropriate privileges) must do the following: openstack server create --flavor $myflavor --image $myimage $servername -As of 21.0.0 (Ussuri), nova supports only specific operations for instances -with accelerators. The lists of supported and unsupported operations are as -below: +Nova supports only specific operations for instances with accelerators. +The lists of supported and unsupported operations are as below: * Supported operations. @@ -42,17 +41,21 @@ below: * Take a snapshot. * Backup. * Rescue and unrescue. + * Rebuild. + * Evacuate. * Unsupported operations - * Rebuild. * Resize. - * Evacuate. * Suspend and resume. * Shelve and unshelve. * Cold migration. * Live migration. +.. versionchanged:: 22.0.0(Victoria) + + Added support for rebuild and evacuate operations. + Some operations, such as lock and unlock, work as they are effectively no-ops for accelerators. diff --git a/nova/accelerator/cyborg.py b/nova/accelerator/cyborg.py index bf74722a424d..b42ddd0e4659 100644 --- a/nova/accelerator/cyborg.py +++ b/nova/accelerator/cyborg.py @@ -302,3 +302,12 @@ class _CyborgClient(object): if err_msg: # No point raising an exception. LOG.error('Failed to delete ARQs %s', arq_uuid_str) + + def get_arq_uuids_for_instance(self, instance): + """Get ARQ UUIDs for the instance. + + :param instance: Instance Object + :return: ARQ UUIDs. + """ + return [arq['uuid'] + for arq in self.get_arqs_for_instance(instance.uuid)] diff --git a/nova/compute/api.py b/nova/compute/api.py index 056100d56b58..d3ddd1bf01f6 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -109,6 +109,8 @@ MIN_COMPUTE_SAME_HOST_COLD_MIGRATE = 48 # TODO(huaqiang): Remove in Wallaby MIN_VER_NOVA_COMPUTE_MIXED_POLICY = 52 +SUPPORT_ACCELERATOR_SERVICE_FOR_REBUILD = 53 + # FIXME(danms): Keep a global cache of the cells we find the # first time we look. This needs to be refreshed on a timer or # trigger. @@ -307,14 +309,27 @@ def _get_image_meta_obj(image_meta_dict): return image_meta -def block_accelerators(func): - @functools.wraps(func) - def wrapper(self, context, instance, *args, **kwargs): - dp_name = instance.flavor.extra_specs.get('accel:device_profile') - if dp_name: - raise exception.ForbiddenWithAccelerators() - return func(self, context, instance, *args, **kwargs) - return wrapper +def block_accelerators(until_service=None): + def inner(func): + @functools.wraps(func) + def wrapper(self, context, instance, *args, **kwargs): + # NOTE(brinzhang): Catch a request operating a mixed instance, + # make sure all nova-compute services have been upgraded and + # support the accelerators. + dp_name = instance.flavor.extra_specs.get('accel:device_profile') + service_support = False + if not dp_name: + service_support = True + elif until_service: + min_version = objects.service.get_minimum_version_all_cells( + nova_context.get_admin_context(), ['nova-compute']) + if min_version >= until_service: + service_support = True + if not service_support: + raise exception.ForbiddenWithAccelerators() + return func(self, context, instance, *args, **kwargs) + return wrapper + return inner @profiler.trace_cls("compute_api") @@ -3393,7 +3408,7 @@ class API(base.Base): fields_obj.Architecture.canonicalize(img_arch) @reject_vtpm_instances(instance_actions.REBUILD) - @block_accelerators + @block_accelerators(until_service=SUPPORT_ACCELERATOR_SERVICE_FOR_REBUILD) # TODO(stephenfin): We should expand kwargs out to named args @check_instance_lock @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED, @@ -3930,7 +3945,7 @@ class API(base.Base): # finally split resize and cold migration into separate code paths # TODO(stephenfin): The 'block_accelerators' decorator doesn't take into # account the accelerators requested in the new flavor - @block_accelerators + @block_accelerators() @check_instance_lock @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED]) @check_instance_host(check_is_up=True) @@ -4159,7 +4174,7 @@ class API(base.Base): return allow_same_host @reject_vtpm_instances(instance_actions.SHELVE) - @block_accelerators + @block_accelerators() @check_instance_lock @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED, vm_states.PAUSED, vm_states.SUSPENDED]) @@ -4324,7 +4339,7 @@ class API(base.Base): return self.compute_rpcapi.get_instance_diagnostics(context, instance=instance) - @block_accelerators + @block_accelerators() @reject_sev_instances(instance_actions.SUSPEND) @check_instance_lock @check_instance_state(vm_state=[vm_states.ACTIVE]) @@ -5028,7 +5043,7 @@ class API(base.Base): diff=diff) return _metadata - @block_accelerators + @block_accelerators() @reject_vtpm_instances(instance_actions.LIVE_MIGRATION) @reject_sev_instances(instance_actions.LIVE_MIGRATION) @check_instance_lock @@ -5160,7 +5175,7 @@ class API(base.Base): instance, migration.id) @reject_vtpm_instances(instance_actions.EVACUATE) - @block_accelerators + @block_accelerators(until_service=SUPPORT_ACCELERATOR_SERVICE_FOR_REBUILD) @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED, vm_states.ERROR]) def evacuate(self, context, instance, host, on_shared_storage, diff --git a/nova/compute/manager.py b/nova/compute/manager.py index b3317db582db..91c621afbf04 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -530,7 +530,7 @@ class ComputeVirtAPI(virtapi.VirtAPI): class ComputeManager(manager.Manager): """Manages the running instances from creation to destruction.""" - target = messaging.Target(version='5.11') + target = messaging.Target(version='5.12') def __init__(self, compute_driver=None, *args, **kwargs): """Load configuration options and connect to the hypervisor.""" @@ -3256,18 +3256,29 @@ class ComputeManager(manager.Manager): migration.status = status migration.save() - def _rebuild_default_impl(self, context, instance, image_meta, - injected_files, admin_password, allocations, - bdms, detach_block_devices, attach_block_devices, - network_info=None, - evacuate=False, block_device_info=None, - preserve_ephemeral=False): + def _rebuild_default_impl( + self, context, instance, image_meta, injected_files, + admin_password, allocations, bdms, detach_block_devices, + attach_block_devices, network_info=None, evacuate=False, + block_device_info=None, preserve_ephemeral=False, + accel_uuids=None): if preserve_ephemeral: # The default code path does not support preserving ephemeral # partitions. raise exception.PreserveEphemeralNotSupported() + accel_info = [] if evacuate: + if instance.flavor.extra_specs.get('accel:device_profile'): + try: + accel_info = self._get_bound_arq_resources( + context, instance, accel_uuids or []) + except (Exception, eventlet.timeout.Timeout) as exc: + LOG.exception(exc) + self._build_resources_cleanup(instance, network_info) + msg = _('Failure getting accelerator resources.') + raise exception.BuildAbortException( + instance_uuid=instance.uuid, reason=msg) detach_block_devices(context, bdms) else: self._power_off_instance(instance, clean_shutdown=True) @@ -3275,6 +3286,14 @@ class ComputeManager(manager.Manager): self.driver.destroy(context, instance, network_info=network_info, block_device_info=block_device_info) + try: + accel_info = self._get_accel_info(context, instance) + except Exception as exc: + LOG.exception(exc) + self._build_resources_cleanup(instance, network_info) + msg = _('Failure getting accelerator resources.') + raise exception.BuildAbortException( + instance_uuid=instance.uuid, reason=msg) instance.task_state = task_states.REBUILD_BLOCK_DEVICE_MAPPING instance.save(expected_task_state=[task_states.REBUILDING]) @@ -3289,7 +3308,8 @@ class ComputeManager(manager.Manager): self.driver.spawn(context, instance, image_meta, injected_files, admin_password, allocations, network_info=network_info, - block_device_info=new_block_device_info) + block_device_info=new_block_device_info, + accel_info=accel_info) def _notify_instance_rebuild_error(self, context, instance, error, bdms): self._notify_about_instance_usage(context, instance, @@ -3298,7 +3318,8 @@ class ComputeManager(manager.Manager): context, instance, self.host, phase=fields.NotificationPhase.ERROR, exception=error, bdms=bdms) - @messaging.expected_exceptions(exception.PreserveEphemeralNotSupported) + @messaging.expected_exceptions(exception.PreserveEphemeralNotSupported, + exception.BuildAbortException) @wrap_exception() @reverts_task_state @wrap_instance_event(prefix='compute') @@ -3307,7 +3328,7 @@ class ComputeManager(manager.Manager): injected_files, new_pass, orig_sys_metadata, bdms, recreate, on_shared_storage, preserve_ephemeral, migration, - scheduled_node, limits, request_spec): + scheduled_node, limits, request_spec, accel_uuids): """Destroy and re-make this instance. A 'rebuild' effectively purges all existing data from the system and @@ -3338,6 +3359,7 @@ class ComputeManager(manager.Manager): :param limits: Overcommit limits set by the scheduler. If a host was specified by the user, this will be None :param request_spec: a RequestSpec object used to schedule the instance + :param accel_uuids: a list of cyborg ARQ uuids. """ # recreate=True means the instance is being evacuated from a failed @@ -3402,7 +3424,7 @@ class ComputeManager(manager.Manager): image_meta, injected_files, new_pass, orig_sys_metadata, bdms, evacuate, on_shared_storage, preserve_ephemeral, migration, request_spec, allocs, rebuild_claim, - scheduled_node, limits) + scheduled_node, limits, accel_uuids) except (exception.ComputeResourcesUnavailable, exception.RescheduledException) as e: if isinstance(e, exception.ComputeResourcesUnavailable): @@ -3469,7 +3491,7 @@ class ComputeManager(manager.Manager): self, context, instance, orig_image_ref, image_meta, injected_files, new_pass, orig_sys_metadata, bdms, evacuate, on_shared_storage, preserve_ephemeral, migration, request_spec, - allocations, rebuild_claim, scheduled_node, limits): + allocations, rebuild_claim, scheduled_node, limits, accel_uuids): """Helper to avoid deep nesting in the top-level method.""" provider_mapping = None @@ -3490,7 +3512,7 @@ class ComputeManager(manager.Manager): context, instance, orig_image_ref, image_meta, injected_files, new_pass, orig_sys_metadata, bdms, evacuate, on_shared_storage, preserve_ephemeral, migration, request_spec, allocations, - provider_mapping) + provider_mapping, accel_uuids) @staticmethod def _get_image_name(image_meta): @@ -3499,12 +3521,12 @@ class ComputeManager(manager.Manager): else: return '' - def _do_rebuild_instance(self, context, instance, orig_image_ref, - image_meta, injected_files, new_pass, - orig_sys_metadata, bdms, evacuate, - on_shared_storage, preserve_ephemeral, - migration, request_spec, allocations, - request_group_resource_providers_mapping): + def _do_rebuild_instance( + self, context, instance, orig_image_ref, image_meta, + injected_files, new_pass, orig_sys_metadata, bdms, evacuate, + on_shared_storage, preserve_ephemeral, migration, request_spec, + allocations, request_group_resource_providers_mapping, + accel_uuids): orig_vm_state = instance.vm_state if evacuate: @@ -3645,7 +3667,8 @@ class ComputeManager(manager.Manager): block_device_info=block_device_info, network_info=network_info, preserve_ephemeral=preserve_ephemeral, - evacuate=evacuate) + evacuate=evacuate, + accel_uuids=accel_uuids) try: with instance.mutated_migration_context(): self.driver.rebuild(**kwargs) diff --git a/nova/compute/rpcapi.py b/nova/compute/rpcapi.py index 74d2f53f3c1f..c2dee603ebea 100644 --- a/nova/compute/rpcapi.py +++ b/nova/compute/rpcapi.py @@ -378,6 +378,8 @@ class ComputeAPI(object): * 5.10 - Add finish_revert_snapshot_based_resize_at_source() * 5.11 - Add accel_uuids (accelerator requests) parameter to build_and_run_instance() + * 5.12 - Add accel_uuids (accelerator requests) parameter to + rebuild_instance() ''' VERSION_ALIASES = { @@ -1056,20 +1058,29 @@ class ComputeAPI(object): block_device_info=block_device_info, reboot_type=reboot_type) - def rebuild_instance(self, ctxt, instance, new_pass, injected_files, + def rebuild_instance( + self, ctxt, instance, new_pass, injected_files, image_ref, orig_image_ref, orig_sys_metadata, bdms, recreate, on_shared_storage, host, node, - preserve_ephemeral, migration, limits, request_spec): + preserve_ephemeral, migration, limits, request_spec, accel_uuids): + # NOTE(edleafe): compute nodes can only use the dict form of limits. if isinstance(limits, objects.SchedulerLimits): limits = limits.to_dict() - msg_args = {'preserve_ephemeral': preserve_ephemeral, - 'migration': migration, - 'scheduled_node': node, - 'limits': limits, - 'request_spec': request_spec} - version = '5.0' + + msg_args = { + 'preserve_ephemeral': preserve_ephemeral, + 'migration': migration, + 'scheduled_node': node, + 'limits': limits, + 'request_spec': request_spec, + 'accel_uuids': accel_uuids + } + version = '5.12' client = self.router.client(ctxt) + if not client.can_send_version(version): + del msg_args['accel_uuids'] + version = '5.0' cctxt = client.prepare(server=_compute_host(host, instance), version=version) cctxt.cast(ctxt, 'rebuild_instance', diff --git a/nova/conductor/manager.py b/nova/conductor/manager.py index 1a3be3e84fac..aa79f8324e03 100644 --- a/nova/conductor/manager.py +++ b/nova/conductor/manager.py @@ -1147,14 +1147,21 @@ class ComputeTaskManager(base.Base): # is not forced to be the original host request_spec.reset_forced_destinations() - port_res_req = ( + external_resources = [] + external_resources += ( self.network_api.get_requested_resource_for_instance( context, instance.uuid)) - # NOTE(gibi): When cyborg or other module wants to handle - # similar non-nova resources then here we have to collect - # all the external resource requests in a single list and + extra_specs = request_spec.flavor.extra_specs + device_profile = extra_specs.get('accel:device_profile') + external_resources.extend( + cyborg.get_device_profile_request_groups( + context, device_profile) + if device_profile else []) + # NOTE(gibi): When other modules want to handle similar + # non-nova resources then here we have to collect all + # the external resource requests in a single list and # add them to the RequestSpec. - request_spec.requested_resources = port_res_req + request_spec.requested_resources = external_resources try: # if this is a rebuild of instance on the same host with @@ -1219,21 +1226,49 @@ class ComputeTaskManager(base.Base): instance.availability_zone = ( availability_zones.get_host_availability_zone( context, host)) + try: + accel_uuids = self._rebuild_cyborg_arq( + context, instance, host, request_spec, evacuate) + except exception.AcceleratorRequestBindingFailed as exc: + cyclient = cyborg.get_client(context) + cyclient.delete_arqs_by_uuid(exc.arqs) + LOG.exception('Failed to rebuild. Reason: %s', exc) + raise exc - self.compute_rpcapi.rebuild_instance(context, - instance=instance, - new_pass=new_pass, - injected_files=injected_files, - image_ref=image_ref, - orig_image_ref=orig_image_ref, - orig_sys_metadata=orig_sys_metadata, - bdms=bdms, - recreate=evacuate, - on_shared_storage=on_shared_storage, - preserve_ephemeral=preserve_ephemeral, - migration=migration, - host=host, node=node, limits=limits, - request_spec=request_spec) + self.compute_rpcapi.rebuild_instance( + context, + instance=instance, + new_pass=new_pass, + injected_files=injected_files, + image_ref=image_ref, + orig_image_ref=orig_image_ref, + orig_sys_metadata=orig_sys_metadata, + bdms=bdms, + recreate=evacuate, + on_shared_storage=on_shared_storage, + preserve_ephemeral=preserve_ephemeral, + migration=migration, + host=host, + node=node, + limits=limits, + request_spec=request_spec, + accel_uuids=accel_uuids) + + def _rebuild_cyborg_arq( + self, context, instance, host, request_spec, evacuate): + dp_name = instance.flavor.extra_specs.get('accel:device_profile') + if not dp_name: + return [] + + cyclient = cyborg.get_client(context) + if not evacuate: + return cyclient.get_arq_uuids_for_instance(instance) + + cyclient.delete_arqs_for_instance(instance.uuid) + resource_provider_mapping = request_spec.get_request_group_mapping() + return self._create_and_bind_arqs( + context, instance.uuid, instance.flavor.extra_specs, + host, resource_provider_mapping) def _validate_image_traits_for_rebuild(self, context, instance, image_ref): """Validates that the traits specified in the image can be satisfied diff --git a/nova/objects/service.py b/nova/objects/service.py index 82203e1246a4..33d1a5e2e8e0 100644 --- a/nova/objects/service.py +++ b/nova/objects/service.py @@ -31,7 +31,7 @@ LOG = logging.getLogger(__name__) # NOTE(danms): This is the global service version counter -SERVICE_VERSION = 52 +SERVICE_VERSION = 53 # NOTE(danms): This is our SERVICE_VERSION history. The idea is that any @@ -187,6 +187,9 @@ SERVICE_VERSION_HISTORY = ( {'compute_rpc': '5.11'}, # Version 52: Add support for the 'mixed' CPU allocation policy {'compute_rpc': '5.11'}, + # Version 53: Compute RPC v5.12: + # Add accel_uuids (accelerator requests) param to rebuild_instance + {'compute_rpc': '5.12'}, ) diff --git a/nova/tests/functional/integrated_helpers.py b/nova/tests/functional/integrated_helpers.py index fc3642165879..828734ca935a 100644 --- a/nova/tests/functional/integrated_helpers.py +++ b/nova/tests/functional/integrated_helpers.py @@ -477,6 +477,13 @@ class InstanceHelperMixin: self.api.post_server_action(server['id'], {'unshelve': {}}) return self._wait_for_state_change(server, expected_state) + def _evacuate_server(self, server, host, expected_state='ACTIVE'): + """Evacuate a server.""" + self.api.post_server_action(server['id'], {'evacuate': {}}) + self._wait_for_server_parameter( + self.server, {'OS-EXT-SRV-ATTR:host': host, + 'status': expected_state}) + class PlacementHelperMixin: """A helper mixin for interacting with placement.""" diff --git a/nova/tests/functional/test_servers.py b/nova/tests/functional/test_servers.py index 73e943a6ecd0..01e702b718cc 100644 --- a/nova/tests/functional/test_servers.py +++ b/nova/tests/functional/test_servers.py @@ -8086,6 +8086,20 @@ class AcceleratorServerOpsTest(AcceleratorServerBase): image_uuid='155d900f-4e14-4e4c-a73d-069cbf4541e6', networks='none', expected_state='ACTIVE') + def _test_evacuate(self, server, num_hosts): + server_hostname = server['OS-EXT-SRV-ATTR:host'] + for i in range(num_hosts): + if self.compute_services[i].host == server_hostname: + compute_to_stop = self.compute_services[i] + else: + compute_to_evacuate = self.compute_services[i] + # Stop and force down the compute service. + compute_id = self.admin_api.get_services( + host=server_hostname, binary='nova-compute')[0]['id'] + compute_to_stop.stop() + self.admin_api.put_service(compute_id, {'forced_down': 'true'}) + return compute_to_stop, compute_to_evacuate + def test_soft_reboot_ok(self): self._reboot_server(self.server) self._check_allocations_usage(self.server) @@ -8160,6 +8174,30 @@ class AcceleratorServerOpsTest(AcceleratorServerBase): self.api.post_server_action(self.server['id'], {'unrescue': {}}) self._check_allocations_usage(self.server) + def test_evacuate_ok(self): + server_hostname = self.server['OS-EXT-SRV-ATTR:host'] + arqs = self.cyborg.fake_get_arqs_for_instance(self.server['id']) + compute_to_stop, compute_to_evacuate = self._test_evacuate( + self.server, self.NUM_HOSTS) + self._evacuate_server(self.server, compute_to_evacuate.host) + compute_to_stop.start() + self.server = self.api.get_server(self.server['id']) + arqs_new = self.cyborg.fake_get_arqs_for_instance(self.server['id']) + evac_hostname = self.server['OS-EXT-SRV-ATTR:host'] + self.assertNotEqual(server_hostname, evac_hostname) + self.assertEqual(server_hostname, arqs[0]['hostname']) + self.assertEqual(evac_hostname, arqs_new[0]['hostname']) + + def test_rebuild_ok(self): + rebuild_image_ref = fake_image.AUTO_DISK_CONFIG_ENABLED_IMAGE_UUID + self.api.post_server_action(self.server['id'], + {'rebuild': { + 'imageRef': rebuild_image_ref, + 'OS-DCF:diskConfig': 'AUTO'}}) + fake_notifier.wait_for_versioned_notifications('instance.rebuild.end') + self._wait_for_state_change(self.server, 'ACTIVE') + self._check_allocations_usage(self.server) + def test_resize_fails(self): ex = self.assertRaises(client.OpenStackApiException, self.api.post_server_action, self.server['id'], @@ -8186,30 +8224,29 @@ class AcceleratorServerOpsTest(AcceleratorServerBase): self.assertEqual(403, ex.response.status_code) self._check_allocations_usage(self.server) - def test_evacuate_fails(self): - server_hostname = self.server['OS-EXT-SRV-ATTR:host'] - for i in range(self.NUM_HOSTS): - hostname = 'accel_host' + str(i) - if hostname != server_hostname: - other_hostname = hostname - if self.compute_services[i].host == server_hostname: - compute_to_stop = self.compute_services[i] - - # Stop and force down the compute service. - compute_id = self.admin_api.get_services( - host=server_hostname, binary='nova-compute')[0]['id'] - compute_to_stop.stop() - self.admin_api.put_service(compute_id, {'forced_down': 'true'}) + @mock.patch.object(objects.service, 'get_minimum_version_all_cells') + def test_evacuate_old_compute(self, old_compute_version): + """Tests when the source compute service is too old to call + evacuate so OpenStackApiException is raised. + """ + old_compute_version.return_value = 52 + _, compute_to_evacuate = self._test_evacuate( + self.server, self.NUM_HOSTS) ex = self.assertRaises(client.OpenStackApiException, self.api.post_server_action, self.server['id'], {'evacuate': { - 'host': other_hostname, + 'host': compute_to_evacuate.host, 'adminPass': 'MySecretPass'}}) self.assertEqual(403, ex.response.status_code) self._check_allocations_usage(self.server) - def test_rebuild_fails(self): + @mock.patch.object(objects.service, 'get_minimum_version_all_cells') + def test_rebuild_old_compute(self, old_compute_version): + """Tests when the source compute service is too old to call + rebuild so OpenStackApiException is raised. + """ + old_compute_version.return_value = 52 rebuild_image_ref = fake_image.AUTO_DISK_CONFIG_ENABLED_IMAGE_UUID ex = self.assertRaises(client.OpenStackApiException, self.api.post_server_action, self.server['id'], diff --git a/nova/tests/unit/accelerator/test_cyborg.py b/nova/tests/unit/accelerator/test_cyborg.py index f0736b2ccf40..67c92b0554ac 100644 --- a/nova/tests/unit/accelerator/test_cyborg.py +++ b/nova/tests/unit/accelerator/test_cyborg.py @@ -23,6 +23,7 @@ from oslo_serialization import jsonutils from nova.accelerator import cyborg from nova import context from nova import exception +from nova import objects from nova.objects import request_spec from nova import test from nova.tests.unit import fake_requests @@ -394,3 +395,25 @@ class CyborgTestCase(test.NoDBTestCase): self.client.ARQ_URL, params={'arqs': arq_uuid_str}) mock_log.assert_called_once_with('Failed to delete ARQs %s', arq_uuid_str) + + @mock.patch('keystoneauth1.adapter.Adapter.get') + def test_get_arq_uuids_for_instance(self, mock_cyborg_get): + # Happy path, without only_resolved=True + _, bound_arqs = self._get_bound_arqs() + instance_uuid = bound_arqs[0]['instance_uuid'] + flavor = objects.Flavor(extra_specs={'accel:device_profile': 'dp1'}) + instance = objects.Instance(flavor=flavor, + uuid=instance_uuid) + query = {"instance": instance_uuid} + content = jsonutils.dumps({'arqs': bound_arqs}) + resp = fake_requests.FakeResponse(200, content) + mock_cyborg_get.return_value = resp + + ret_arqs = self.client.get_arq_uuids_for_instance(instance) + + mock_cyborg_get.assert_called_once_with( + self.client.ARQ_URL, params=query) + bound_arqs = [bound_arq['uuid'] for bound_arq in bound_arqs] + bound_arqs.sort() + ret_arqs.sort() + self.assertEqual(bound_arqs, ret_arqs) diff --git a/nova/tests/unit/compute/test_api.py b/nova/tests/unit/compute/test_api.py index 9ddfd3231a6d..6ff7244dbe69 100644 --- a/nova/tests/unit/compute/test_api.py +++ b/nova/tests/unit/compute/test_api.py @@ -2122,7 +2122,7 @@ class _ComputeAPIUnitTestMixIn(object): fake_inst = self._create_instance_obj() fake_inst.flavor = cur_flavor new_flavor = objects.Flavor(id=2, name='bar', vcpus=1, memory_mb=2048, - root_gb=10, disabled=False) + root_gb=10, disabled=False, extra_specs={}) mock_get.return_value = new_flavor mock_check.side_effect = exception.OverQuota( overs=['ram'], quotas={'cores': 1, 'ram': 2048}, @@ -7569,8 +7569,9 @@ class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase): mock_get_min_ver.assert_called_once_with( self.context, ['nova-compute']) - def _test_block_accelerators(self, instance, args_info): - @compute_api.block_accelerators + def _test_block_accelerators(self, instance, args_info, + until_service=None): + @compute_api.block_accelerators(until_service=until_service) def myfunc(self, context, instance, *args, **kwargs): args_info['args'] = (context, instance, *args) args_info['kwargs'] = dict(**kwargs) @@ -7604,6 +7605,37 @@ class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase): # myfunc was not called self.assertEqual({}, args_info) + @mock.patch('nova.objects.service.get_minimum_version_all_cells', + return_value=54) + def test_block_accelerators_until_service(self, mock_get_min): + """Support operating server with acclerators until compute service + more than the version of 53. + """ + extra_specs = {'accel:device_profile': 'mydp'} + flavor = self._create_flavor(extra_specs=extra_specs) + instance = self._create_instance_obj(flavor=flavor) + args_info = {} + expected_args, kwargs = self._test_block_accelerators( + instance, args_info, until_service=53) + self.assertEqual(expected_args, args_info['args']) + self.assertEqual(kwargs, args_info['kwargs']) + + @mock.patch('nova.objects.service.get_minimum_version_all_cells', + return_value=52) + def test_block_accelerators_until_service_forbidden(self, mock_get_min): + """Ensure a 'ForbiddenWithAccelerators' exception raises if any + compute service less than the version of 53. + """ + extra_specs = {'accel:device_profile': 'mydp'} + flavor = self._create_flavor(extra_specs=extra_specs) + instance = self._create_instance_obj(flavor=flavor) + args_info = {} + self.assertRaisesRegex(exception.ForbiddenWithAccelerators, + 'Forbidden with instances that have accelerators.', + self._test_block_accelerators, instance, args_info, 53) + # myfunc was not called + self.assertEqual({}, args_info) + # TODO(huaqiang): Remove in Wallaby @mock.patch('nova.objects.service.get_minimum_version_all_cells') def test__check_compute_service_for_mixed_instance(self, mock_ver): diff --git a/nova/tests/unit/compute/test_compute.py b/nova/tests/unit/compute/test_compute.py index d604a67516b1..fb045f534356 100644 --- a/nova/tests/unit/compute/test_compute.py +++ b/nova/tests/unit/compute/test_compute.py @@ -2743,16 +2743,12 @@ class ComputeTestCase(BaseTestCase, block_device_mapping=[]) db.instance_update(self.context, instance['uuid'], {"task_state": task_states.REBUILDING}) - self.compute.rebuild_instance(self.context, instance, - image_ref, image_ref, - injected_files=[], - new_pass="new_password", - orig_sys_metadata=sys_metadata, - bdms=[], recreate=False, - on_shared_storage=False, - preserve_ephemeral=False, - migration=None, scheduled_node=None, - limits={}, request_spec=None) + self.compute.rebuild_instance( + self.context, instance, image_ref, image_ref, injected_files=[], + new_pass="new_password", orig_sys_metadata=sys_metadata, + bdms=[], recreate=False, on_shared_storage=False, + preserve_ephemeral=False, migration=None, scheduled_node=None, + limits={}, request_spec=None, accel_uuids=[]) self.compute.terminate_instance(self.context, instance, []) def test_rebuild_driver(self): @@ -2777,16 +2773,12 @@ class ComputeTestCase(BaseTestCase, block_device_mapping=[]) db.instance_update(self.context, instance['uuid'], {"task_state": task_states.REBUILDING}) - self.compute.rebuild_instance(self.context, instance, - image_ref, image_ref, - injected_files=[], - new_pass="new_password", - orig_sys_metadata=sys_metadata, - bdms=[], recreate=False, - on_shared_storage=False, - preserve_ephemeral=False, migration=None, - scheduled_node=None, limits={}, - request_spec=None) + self.compute.rebuild_instance( + self.context, instance, image_ref, image_ref, injected_files=[], + new_pass="new_password", orig_sys_metadata=sys_metadata, + bdms=[], recreate=False, on_shared_storage=False, + preserve_ephemeral=False, migration=None, scheduled_node=None, + limits={}, request_spec=None, accel_uuids=[]) self.assertTrue(called['rebuild']) self.compute.terminate_instance(self.context, instance, []) @@ -2833,16 +2825,12 @@ class ComputeTestCase(BaseTestCase, block_device_mapping=[]) db.instance_update(self.context, instance['uuid'], {"task_state": task_states.REBUILDING}) - self.compute.rebuild_instance(self.context, instance, - image_ref, image_ref, - injected_files=[], - new_pass="new_password", - orig_sys_metadata=sys_metadata, - bdms=bdms, recreate=False, - preserve_ephemeral=False, migration=None, - scheduled_node=None, limits={}, - on_shared_storage=False, - request_spec=None) + self.compute.rebuild_instance( + self.context, instance, image_ref, image_ref, injected_files=[], + new_pass="new_password", orig_sys_metadata=sys_metadata, + bdms=bdms, recreate=False, preserve_ephemeral=False, + migration=None, scheduled_node=None, limits={}, + on_shared_storage=False, request_spec=None, accel_uuids=[]) self.assertTrue(called['rebuild']) self.compute.terminate_instance(self.context, instance, []) @@ -2856,14 +2844,12 @@ class ComputeTestCase(BaseTestCase, block_device_mapping=[]) db.instance_update(self.context, instance['uuid'], {"task_state": task_states.REBUILDING}) - self.compute.rebuild_instance(self.context, instance, - '', '', injected_files=[], - new_pass="new_password", - orig_sys_metadata=sys_metadata, bdms=[], - recreate=False, on_shared_storage=False, - preserve_ephemeral=False, migration=None, - scheduled_node=None, limits=None, - request_spec=None) + self.compute.rebuild_instance( + self.context, instance, '', '', injected_files=[], + new_pass="new_password", orig_sys_metadata=sys_metadata, bdms=[], + recreate=False, on_shared_storage=False, preserve_ephemeral=False, + migration=None, scheduled_node=None, limits=None, + request_spec=None, accel_uuids=[]) self.compute.terminate_instance(self.context, instance, []) def test_rebuild_launched_at_time(self): @@ -2879,16 +2865,12 @@ class ComputeTestCase(BaseTestCase, time_fixture.advance_time_delta(cur_time - old_time) db.instance_update(self.context, instance['uuid'], {"task_state": task_states.REBUILDING}) - self.compute.rebuild_instance(self.context, instance, - image_ref, image_ref, - injected_files=[], - new_pass="new_password", - orig_sys_metadata={}, - bdms=[], recreate=False, - on_shared_storage=False, - preserve_ephemeral=False, migration=None, - scheduled_node=None, limits={}, - request_spec=None) + self.compute.rebuild_instance( + self.context, instance, image_ref, image_ref, injected_files=[], + new_pass="new_password", orig_sys_metadata={}, bdms=[], + recreate=False, on_shared_storage=False, preserve_ephemeral=False, + migration=None, scheduled_node=None, limits={}, request_spec=None, + accel_uuids=[]) instance.refresh() self.assertEqual(cur_time, instance['launched_at'].replace(tzinfo=None)) @@ -2916,16 +2898,12 @@ class ComputeTestCase(BaseTestCase, instance['uuid']) db.instance_update(self.context, instance['uuid'], {"task_state": task_states.REBUILDING}) - self.compute.rebuild_instance(self.context, instance, - image_ref, image_ref, - injected_files=injected_files, - new_pass="new_password", - orig_sys_metadata=sys_metadata, - bdms=[], recreate=False, - on_shared_storage=False, - preserve_ephemeral=False, - migration=None, scheduled_node=None, - limits={}, request_spec=None) + self.compute.rebuild_instance( + self.context, instance, image_ref, image_ref, + injected_files=injected_files, new_pass="new_password", + orig_sys_metadata=sys_metadata, bdms=[], recreate=False, + on_shared_storage=False, preserve_ephemeral=False, migration=None, + scheduled_node=None, limits={}, request_spec=None, accel_uuids=[]) self.compute.terminate_instance(self.context, instance, []) @mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid') @@ -4654,7 +4632,8 @@ class ComputeTestCase(BaseTestCase, 'scheduled_node': None, 'limits': {}, 'request_spec': None, - 'on_shared_storage': False}), + 'on_shared_storage': False, + 'accel_uuids': ()}), ("set_admin_password", task_states.UPDATING_PASSWORD, {'new_pass': None}), ("rescue_instance", task_states.RESCUING, @@ -5166,17 +5145,12 @@ class ComputeTestCase(BaseTestCase, inst_ref.task_state = task_states.REBUILDING inst_ref.save() - self.compute.rebuild_instance(self.context, - inst_ref, - image_ref, new_image_ref, - injected_files=[], - new_pass=password, - orig_sys_metadata=orig_sys_metadata, - bdms=[], recreate=False, - on_shared_storage=False, - preserve_ephemeral=False, migration=None, - scheduled_node=None, limits={}, - request_spec=None) + self.compute.rebuild_instance( + self.context, inst_ref, image_ref, new_image_ref, + injected_files=[], new_pass=password, + orig_sys_metadata=orig_sys_metadata, bdms=[], recreate=False, + on_shared_storage=False, preserve_ephemeral=False, migration=None, + scheduled_node=None, limits={}, request_spec=None, accel_uuids=[]) inst_ref.refresh() @@ -12818,7 +12792,7 @@ class EvacuateHostTestCase(BaseTestCase): image_ref, injected_files, 'newpass', {}, bdms, recreate=True, on_shared_storage=on_shared_storage, migration=migration, preserve_ephemeral=False, scheduled_node=node, limits=limits, - request_spec=None) + request_spec=None, accel_uuids=[]) if vm_states_is_stopped: mock_notify_rebuild.assert_has_calls([ mock.call(ctxt, self.inst, self.inst.host, phase='start', @@ -13007,7 +12981,7 @@ class EvacuateHostTestCase(BaseTestCase): test.MatchType(objects.ImageMeta), mock.ANY, 'newpass', mock.ANY, network_info=mock.ANY, - block_device_info=mock.ANY) + block_device_info=mock.ANY, accel_info=mock.ANY) @mock.patch.object(fake.FakeDriver, 'spawn') def test_rebuild_on_host_without_shared_storage(self, mock_spawn): @@ -13025,7 +12999,7 @@ class EvacuateHostTestCase(BaseTestCase): test.MatchType(objects.ImageMeta), mock.ANY, 'newpass', mock.ANY, network_info=mock.ANY, - block_device_info=mock.ANY) + block_device_info=mock.ANY, accel_info=mock.ANY) def test_rebuild_on_host_instance_exists(self): """Rebuild if instance exists raises an exception.""" @@ -13069,7 +13043,7 @@ class EvacuateHostTestCase(BaseTestCase): test.MatchObjPrims(self.inst.image_meta), mock.ANY, 'newpass', mock.ANY, network_info=mock.ANY, - block_device_info=mock.ANY) + block_device_info=mock.ANY, accel_info=mock.ANY) @mock.patch.object(fake.FakeDriver, 'spawn') def test_on_shared_storage_not_provided_host_with_shared_storage(self, @@ -13085,7 +13059,7 @@ class EvacuateHostTestCase(BaseTestCase): test.MatchType(objects.ImageMeta), mock.ANY, 'newpass', mock.ANY, network_info=mock.ANY, - block_device_info=mock.ANY) + block_device_info=mock.ANY, accel_info=mock.ANY) def test_rebuild_migration_passed_in(self): migration = mock.Mock(spec=objects.Migration) @@ -13128,7 +13102,7 @@ class EvacuateHostTestCase(BaseTestCase): 'nova.scheduler.client.report.SchedulerReportClient.' 'remove_provider_tree_from_instance_allocation') with patch_spawn, patch_on_disk, patch_claim, patch_remove_allocs: - self.assertRaises(exception.BuildAbortException, + self.assertRaises(messaging.ExpectedException, self._rebuild, migration=migration, send_node=True) self.assertEqual("failed", migration.status) @@ -13162,7 +13136,7 @@ class EvacuateHostTestCase(BaseTestCase): # NOTE(ndipanov): Make sure that we pass the topology from the context def fake_spawn(context, instance, image_meta, injected_files, admin_password, allocations, network_info=None, - block_device_info=None): + block_device_info=None, accel_info=None): self.assertIsNone(instance.numa_topology) self.inst.numa_topology = numa_topology diff --git a/nova/tests/unit/compute/test_compute_mgr.py b/nova/tests/unit/compute/test_compute_mgr.py index c8a483516f1d..d37618c80964 100644 --- a/nova/tests/unit/compute/test_compute_mgr.py +++ b/nova/tests/unit/compute/test_compute_mgr.py @@ -5138,10 +5138,9 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase, mock_rebuild.side_effect = exc - self.compute.rebuild_instance(self.context, instance, None, None, None, - None, None, None, recreate, - False, False, None, scheduled_node, {}, - None) + self.compute.rebuild_instance( + self.context, instance, None, None, None, None, None, None, + recreate, False, False, None, scheduled_node, {}, None, []) mock_set.assert_called_once_with(None, 'failed') mock_notify_about_instance_usage.assert_called_once_with( mock.ANY, instance, 'rebuild.error', fault=mock_rebuild.side_effect @@ -5205,7 +5204,7 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase, instance = fake_instance.fake_instance_obj(self.context) instance.vm_state = vm_states.ACTIVE ex = exception.ComputeResourcesUnavailable(reason='out of foo') - self.assertRaises(exception.BuildAbortException, + self.assertRaises(messaging.ExpectedException, self._test_rebuild_ex, instance, ex) # Make sure the instance vm_state did not change. self.assertEqual(vm_states.ACTIVE, instance.vm_state) @@ -5252,7 +5251,7 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase, None, recreate=True, on_shared_storage=None, preserve_ephemeral=False, migration=None, scheduled_node='fake-node', - limits={}, request_spec=request_spec) + limits={}, request_spec=request_spec, accel_uuids=[]) mock_validate_policy.assert_called_once_with( elevated_context, instance, {'group': [uuids.group]}) @@ -5287,11 +5286,11 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase, mock_validate_policy.side_effect = exc self.assertRaises( - exception.BuildAbortException, self.compute.rebuild_instance, + messaging.ExpectedException, self.compute.rebuild_instance, self.context, instance, None, None, None, None, None, None, recreate=True, on_shared_storage=None, preserve_ephemeral=False, migration=None, scheduled_node='fake-node', limits={}, - request_spec=request_spec) + request_spec=request_spec, accel_uuids=[]) mock_validate_policy.assert_called_once_with( elevated_context, instance, {'group': [uuids.group]}) @@ -5313,9 +5312,10 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase, mock.patch.object(objects.Instance, 'save'), mock.patch.object(self.compute, '_set_migration_status'), ) as (mock_get, mock_rebuild, mock_save, mock_set): - self.compute.rebuild_instance(self.context, instance, None, None, - None, None, None, None, False, - False, False, None, None, {}, None) + self.compute.rebuild_instance( + self.context, instance, None, None, + None, None, None, None, False, + False, False, None, None, {}, None, []) self.assertFalse(mock_get.called) self.assertEqual(node, instance.node) mock_set.assert_called_once_with(None, 'done') @@ -5335,9 +5335,9 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase, mock.patch.object(self.compute, '_set_migration_status'), ) as (mock_get, mock_rebuild, mock_save, mock_set): mock_get.return_value.hypervisor_hostname = 'new-node' - self.compute.rebuild_instance(self.context, instance, None, None, - None, None, None, None, True, - False, False, None, None, {}, None) + self.compute.rebuild_instance( + self.context, instance, None, None, None, None, None, + None, True, False, False, None, None, {}, None, []) mock_get.assert_called_once_with(mock.ANY, self.compute.host) self.assertEqual('new-node', instance.node) mock_set.assert_called_once_with(None, 'done') @@ -5419,7 +5419,7 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase, recreate, on_shared_storage, preserve_ephemeral, {}, {}, self.allocations, - mock.sentinel.mapping) + mock.sentinel.mapping, []) mock_notify_usage.assert_has_calls( [mock.call(self.context, instance, "rebuild.start", @@ -5450,7 +5450,7 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase, def _spawn(context, instance, image_meta, injected_files, admin_password, allocations, network_info=None, - block_device_info=None): + block_device_info=None, accel_info=None): self.assertEqual(block_device_info['block_device_mapping'], 'shared_block_storage') @@ -5462,12 +5462,15 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase, mock.patch.object(objects.Instance, 'save', return_value=None), mock.patch.object(self.compute, '_power_off_instance', - return_value=None) + return_value=None), + mock.patch.object(self.compute, '_get_accel_info', + return_value=[]) ) as( mock_destroy, mock_spawn, mock_save, - mock_power_off + mock_power_off, + mock_accel_info ): instance = fake_instance.fake_instance_obj(self.context) instance.migration_context = None @@ -5518,7 +5521,8 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase, preserve_ephemeral=False, migration=objects.Migration(), request_spec=objects.RequestSpec(), allocations=self.allocations, - request_group_resource_providers_mapping=mock.sentinel.mapping) + request_group_resource_providers_mapping=mock.sentinel.mapping, + accel_uuids=[]) self.assertIn('Trusted image certificates provided on host', six.text_type(ex)) diff --git a/nova/tests/unit/compute/test_rpcapi.py b/nova/tests/unit/compute/test_rpcapi.py index 42de77a5889f..4336991e2034 100644 --- a/nova/tests/unit/compute/test_rpcapi.py +++ b/nova/tests/unit/compute/test_rpcapi.py @@ -693,12 +693,55 @@ class ComputeRpcAPITestCase(test.NoDBTestCase): reboot_type='type') def test_rebuild_instance(self): - self._test_compute_api('rebuild_instance', 'cast', new_pass='None', - injected_files='None', image_ref='None', orig_image_ref='None', - bdms=[], instance=self.fake_instance_obj, host='new_host', - orig_sys_metadata=None, recreate=True, on_shared_storage=True, - preserve_ephemeral=True, migration=None, node=None, - limits=None, request_spec=None, version='5.0') + # With rpcapi 5.12, when a list of accel_uuids is passed as a param, + # that list must be passed to the client. That is tested in + # _test_compute_api with rpc_mock.assert, where expected_kwargs + # must have the accel_uuids. + self._test_compute_api( + 'rebuild_instance', 'cast', new_pass='None', + injected_files='None', image_ref='None', orig_image_ref='None', + bdms=[], instance=self.fake_instance_obj, host='new_host', + orig_sys_metadata=None, recreate=True, on_shared_storage=True, + preserve_ephemeral=True, migration=None, node=None, + limits=None, request_spec=None, accel_uuids=[], version='5.12') + + def test_rebuild_instance_old_rpcapi(self): + # With rpcapi < 5.12, accel_uuids must be dropped in the client call. + ctxt = context.RequestContext('fake_user', 'fake_project') + compute_api = compute_rpcapi.ComputeAPI() + compute_api.router.client = mock.Mock() + mock_client = mock.MagicMock() + compute_api.router.client.return_value = mock_client + # Force can_send_version to False, so that 5.0 version is used. + mock_client.can_send_version.return_value = False + mock_cctx = mock.MagicMock() + mock_client.prepare.return_value = mock_cctx + rebuild_args = { + 'new_pass': 'admin_password', + 'injected_files': 'files_to_inject', + 'image_ref': uuids.image_ref, + 'orig_image_ref': uuids.orig_image_ref, + 'orig_sys_metadata': 'orig_sys_meta', + 'bdms': {}, + 'recreate': False, + 'on_shared_storage': False, + 'preserve_ephemeral': False, + 'request_spec': None, + 'migration': None, + 'limits': None + } + compute_api.rebuild_instance( + ctxt, instance=self.fake_instance_obj, + accel_uuids=['938af7f9-f136-4e5a-bdbe-3b6feab54311'], + node=None, host=None, **rebuild_args) + + mock_client.can_send_version.assert_called_once_with('5.12') + mock_client.prepare.assert_called_with( + server=self.fake_instance_obj.host, version='5.0') + mock_cctx.cast.assert_called_with( # No accel_uuids + ctxt, 'rebuild_instance', + instance=self.fake_instance_obj, + scheduled_node=None, **rebuild_args) def test_reserve_block_device_name(self): self.flags(long_rpc_timeout=1234) diff --git a/nova/tests/unit/conductor/test_conductor.py b/nova/tests/unit/conductor/test_conductor.py index 946685fce810..12158cb343b9 100644 --- a/nova/tests/unit/conductor/test_conductor.py +++ b/nova/tests/unit/conductor/test_conductor.py @@ -390,6 +390,7 @@ class _BaseTaskTestCase(object): compute_rebuild_args['migration'] = migration compute_rebuild_args['node'] = node compute_rebuild_args['limits'] = limits + compute_rebuild_args['accel_uuids'] = [] return rebuild_args, compute_rebuild_args @@ -1780,6 +1781,7 @@ class _BaseTaskTestCase(object): rebuild_args, _ = self._prepare_rebuild_args( {'host': None, 'recreate': True}) fake_spec = objects.RequestSpec() + fake_spec.flavor = inst_obj.flavor rebuild_args['request_spec'] = fake_spec with test.nested( @@ -1847,6 +1849,7 @@ class _BaseTaskTestCase(object): fake_selection = objects.Selection(service_host=expected_host, nodename=expected_node, limits=None) fake_spec = objects.RequestSpec(ignore_hosts=[uuids.ignored_host]) + fake_spec.flavor = inst_obj.flavor rebuild_args, compute_args = self._prepare_rebuild_args( {'host': None, 'node': expected_node, 'limits': expected_limits, 'request_spec': fake_spec, 'recreate': True}) diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 2e78333410c2..05d2dabf8066 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -286,7 +286,7 @@ class ComputeDriver(object): admin_password, allocations, bdms, detach_block_devices, attach_block_devices, network_info=None, evacuate=False, block_device_info=None, - preserve_ephemeral=False): + preserve_ephemeral=False, accel_uuids=None): """Destroy and re-make this instance. A 'rebuild' effectively purges all existing data from the system and @@ -323,6 +323,7 @@ class ComputeDriver(object): attached to the instance. :param preserve_ephemeral: True if the default ephemeral storage partition must be preserved on rebuild + :param accel_uuids: Accelerator UUIDs. """ raise NotImplementedError() diff --git a/nova/virt/ironic/driver.py b/nova/virt/ironic/driver.py index 5aee1ea3bcbc..aa00ef68395a 100644 --- a/nova/virt/ironic/driver.py +++ b/nova/virt/ironic/driver.py @@ -1670,7 +1670,7 @@ class IronicDriver(virt_driver.ComputeDriver): admin_password, allocations, bdms, detach_block_devices, attach_block_devices, network_info=None, evacuate=False, block_device_info=None, - preserve_ephemeral=False): + preserve_ephemeral=False, accel_uuids=None): """Rebuild/redeploy an instance. This version of rebuild() allows for supporting the option to @@ -1710,7 +1710,7 @@ class IronicDriver(virt_driver.ComputeDriver): information. Ignored by this driver. :param preserve_ephemeral: Boolean value; if True the ephemeral must be preserved on rebuild. - + :param accel_uuids: Accelerator UUIDs. Ignored by this driver. """ LOG.debug('Rebuild called for instance', instance=instance)