Merge "Move rebuild to conductor and add find host logic"
This commit is contained in:
		@@ -2204,11 +2204,12 @@ class API(base.Base):
 | 
			
		||||
 | 
			
		||||
        self._record_action_start(context, instance, instance_actions.REBUILD)
 | 
			
		||||
 | 
			
		||||
        self.compute_rpcapi.rebuild_instance(context, instance=instance,
 | 
			
		||||
        self.compute_task_api.rebuild_instance(context, instance=instance,
 | 
			
		||||
                new_pass=admin_password, injected_files=files_to_inject,
 | 
			
		||||
                image_ref=image_href, orig_image_ref=orig_image_ref,
 | 
			
		||||
                orig_sys_metadata=orig_sys_metadata, bdms=bdms,
 | 
			
		||||
                preserve_ephemeral=preserve_ephemeral, kwargs=kwargs)
 | 
			
		||||
                preserve_ephemeral=preserve_ephemeral, host=instance.host,
 | 
			
		||||
                kwargs=kwargs)
 | 
			
		||||
 | 
			
		||||
    @wrap_check_policy
 | 
			
		||||
    @check_instance_lock
 | 
			
		||||
@@ -3052,6 +3053,12 @@ class API(base.Base):
 | 
			
		||||
 | 
			
		||||
        Checking vm compute host state, if the host not in expected_state,
 | 
			
		||||
        raising an exception.
 | 
			
		||||
 | 
			
		||||
        :param instance: The instance to evacuate
 | 
			
		||||
        :param host: Target host. if not set, the scheduler will pick up one
 | 
			
		||||
        :param on_shared_storage: True if instance files on shared storage
 | 
			
		||||
        :param admin_password: password to set on rebuilt instance
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        LOG.debug('vm evacuation scheduled')
 | 
			
		||||
        inst_host = instance.host
 | 
			
		||||
@@ -3066,17 +3073,17 @@ class API(base.Base):
 | 
			
		||||
        instance.save(expected_task_state=[None])
 | 
			
		||||
        self._record_action_start(context, instance, instance_actions.EVACUATE)
 | 
			
		||||
 | 
			
		||||
        return self.compute_rpcapi.rebuild_instance(context,
 | 
			
		||||
                                        instance=instance,
 | 
			
		||||
                                        new_pass=admin_password,
 | 
			
		||||
                                        injected_files=None,
 | 
			
		||||
                                        image_ref=None,
 | 
			
		||||
                                        orig_image_ref=None,
 | 
			
		||||
                                        orig_sys_metadata=None,
 | 
			
		||||
                                        bdms=None,
 | 
			
		||||
                                        recreate=True,
 | 
			
		||||
                                        on_shared_storage=on_shared_storage,
 | 
			
		||||
                                        host=host)
 | 
			
		||||
        return self.compute_task_api.rebuild_instance(context,
 | 
			
		||||
                       instance=instance,
 | 
			
		||||
                       new_pass=admin_password,
 | 
			
		||||
                       injected_files=None,
 | 
			
		||||
                       image_ref=None,
 | 
			
		||||
                       orig_image_ref=None,
 | 
			
		||||
                       orig_sys_metadata=None,
 | 
			
		||||
                       bdms=None,
 | 
			
		||||
                       recreate=True,
 | 
			
		||||
                       on_shared_storage=on_shared_storage,
 | 
			
		||||
                       host=host)
 | 
			
		||||
 | 
			
		||||
    def get_migrations(self, context, filters):
 | 
			
		||||
        """Get all migrations for the given filters."""
 | 
			
		||||
 
 | 
			
		||||
@@ -260,6 +260,24 @@ class LocalComputeTaskAPI(object):
 | 
			
		||||
        utils.spawn_n(self._manager.unshelve_instance, context,
 | 
			
		||||
                instance=instance)
 | 
			
		||||
 | 
			
		||||
    def rebuild_instance(self, context, instance, orig_image_ref, image_ref,
 | 
			
		||||
                         injected_files, new_pass, orig_sys_metadata,
 | 
			
		||||
                         bdms, recreate=False, on_shared_storage=False,
 | 
			
		||||
                         preserve_ephemeral=False, host=None, kwargs=None):
 | 
			
		||||
        # kwargs unused but required for cell compatibility.
 | 
			
		||||
        utils.spawn_n(self._manager.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=recreate,
 | 
			
		||||
                on_shared_storage=on_shared_storage,
 | 
			
		||||
                host=host,
 | 
			
		||||
                preserve_ephemeral=preserve_ephemeral)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class API(LocalAPI):
 | 
			
		||||
    """Conductor API that does updates via RPC to the ConductorManager."""
 | 
			
		||||
@@ -351,3 +369,21 @@ class ComputeTaskAPI(object):
 | 
			
		||||
    def unshelve_instance(self, context, instance):
 | 
			
		||||
        self.conductor_compute_rpcapi.unshelve_instance(context,
 | 
			
		||||
                instance=instance)
 | 
			
		||||
 | 
			
		||||
    def rebuild_instance(self, context, instance, orig_image_ref, image_ref,
 | 
			
		||||
                         injected_files, new_pass, orig_sys_metadata,
 | 
			
		||||
                         bdms, recreate=False, on_shared_storage=False,
 | 
			
		||||
                         preserve_ephemeral=False, host=None, kwargs=None):
 | 
			
		||||
        # kwargs unused but required for cell compatibility
 | 
			
		||||
        self.conductor_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=recreate,
 | 
			
		||||
                on_shared_storage=on_shared_storage,
 | 
			
		||||
                preserve_ephemeral=preserve_ephemeral,
 | 
			
		||||
                host=host)
 | 
			
		||||
 
 | 
			
		||||
@@ -452,7 +452,7 @@ class ComputeTaskManager(base.Base):
 | 
			
		||||
    may involve coordinating activities on multiple compute nodes.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    target = messaging.Target(namespace='compute_task', version='1.7')
 | 
			
		||||
    target = messaging.Target(namespace='compute_task', version='1.8')
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super(ComputeTaskManager, self).__init__()
 | 
			
		||||
@@ -708,3 +708,44 @@ class ComputeTaskManager(base.Base):
 | 
			
		||||
                del(sys_meta[key])
 | 
			
		||||
        instance.system_metadata = sys_meta
 | 
			
		||||
        instance.save()
 | 
			
		||||
 | 
			
		||||
    def rebuild_instance(self, context, instance, orig_image_ref, image_ref,
 | 
			
		||||
                         injected_files, new_pass, orig_sys_metadata,
 | 
			
		||||
                         bdms, recreate, on_shared_storage,
 | 
			
		||||
                         preserve_ephemeral=False, host=None):
 | 
			
		||||
 | 
			
		||||
        with compute_utils.EventReporter(context, 'rebuild_server',
 | 
			
		||||
                                          instance.uuid):
 | 
			
		||||
            if not host:
 | 
			
		||||
                # NOTE(lcostantino): Retrieve scheduler filters for the
 | 
			
		||||
                # instance when the feature is available
 | 
			
		||||
                filter_properties = {'ignore_hosts': [instance.host]}
 | 
			
		||||
                request_spec = scheduler_utils.build_request_spec(context,
 | 
			
		||||
                                                                  image_ref,
 | 
			
		||||
                                                                  [instance])
 | 
			
		||||
                try:
 | 
			
		||||
                    hosts = self.scheduler_rpcapi.select_destinations(context,
 | 
			
		||||
                                                            request_spec,
 | 
			
		||||
                                                            filter_properties)
 | 
			
		||||
                    host = hosts.pop(0)['host']
 | 
			
		||||
                except exception.NoValidHost as ex:
 | 
			
		||||
                    with excutils.save_and_reraise_exception():
 | 
			
		||||
                        self._set_vm_state_and_notify(context,
 | 
			
		||||
                                'rebuild_server',
 | 
			
		||||
                                {'vm_state': instance.vm_state,
 | 
			
		||||
                                 'task_state': None}, ex, request_spec)
 | 
			
		||||
                        LOG.warning(_("No valid host found for rebuild"),
 | 
			
		||||
                                      instance=instance)
 | 
			
		||||
 | 
			
		||||
            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=recreate,
 | 
			
		||||
                    on_shared_storage=on_shared_storage,
 | 
			
		||||
                    preserve_ephemeral=preserve_ephemeral,
 | 
			
		||||
                    host=host)
 | 
			
		||||
 
 | 
			
		||||
@@ -367,6 +367,7 @@ class ComputeTaskAPI(object):
 | 
			
		||||
    1.5 - Added the leagacy_bdm parameter to build_instances
 | 
			
		||||
    1.6 - Made migrate_server use instance objects
 | 
			
		||||
    1.7 - Do not send block_device_mapping and legacy_bdm to build_instances
 | 
			
		||||
    1.8 - Add rebuild_instance
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
@@ -418,3 +419,17 @@ class ComputeTaskAPI(object):
 | 
			
		||||
    def unshelve_instance(self, context, instance):
 | 
			
		||||
        cctxt = self.client.prepare(version='1.3')
 | 
			
		||||
        cctxt.cast(context, 'unshelve_instance', instance=instance)
 | 
			
		||||
 | 
			
		||||
    def rebuild_instance(self, ctxt, instance, new_pass, injected_files,
 | 
			
		||||
            image_ref, orig_image_ref, orig_sys_metadata, bdms,
 | 
			
		||||
            recreate=False, on_shared_storage=False, host=None,
 | 
			
		||||
            preserve_ephemeral=False, kwargs=None):
 | 
			
		||||
        cctxt = self.client.prepare(version='1.8')
 | 
			
		||||
        cctxt.cast(ctxt, 'rebuild_instance',
 | 
			
		||||
                   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=recreate, on_shared_storage=on_shared_storage,
 | 
			
		||||
                   preserve_ephemeral=preserve_ephemeral,
 | 
			
		||||
                   host=host)
 | 
			
		||||
 
 | 
			
		||||
@@ -7208,7 +7208,7 @@ class ComputeAPITestCase(BaseTestCase):
 | 
			
		||||
            info['image_ref'] = kwargs['instance'].image_ref
 | 
			
		||||
            info['clean'] = kwargs['instance'].obj_what_changed() == set()
 | 
			
		||||
 | 
			
		||||
        self.stubs.Set(self.compute_api.compute_rpcapi, 'rebuild_instance',
 | 
			
		||||
        self.stubs.Set(self.compute_api.compute_task_api, 'rebuild_instance',
 | 
			
		||||
                       fake_rpc_rebuild)
 | 
			
		||||
 | 
			
		||||
        image_ref = instance["image_ref"] + '-new_image_ref'
 | 
			
		||||
@@ -9195,7 +9195,7 @@ class ComputeAPITestCase(BaseTestCase):
 | 
			
		||||
 | 
			
		||||
        self.stubs.Set(self.compute_api.servicegroup_api, 'service_is_up',
 | 
			
		||||
                fake_service_is_up)
 | 
			
		||||
        self.stubs.Set(self.compute_api.compute_rpcapi, 'rebuild_instance',
 | 
			
		||||
        self.stubs.Set(self.compute_api.compute_task_api, 'rebuild_instance',
 | 
			
		||||
                fake_rebuild_instance)
 | 
			
		||||
        self.compute_api.evacuate(self.context.elevated(),
 | 
			
		||||
                                  instance,
 | 
			
		||||
 
 | 
			
		||||
@@ -1851,7 +1851,7 @@ class _ComputeAPIUnitTestMixIn(object):
 | 
			
		||||
        _get_image.return_value = (None, image)
 | 
			
		||||
        bdm_get_by_instance_uuid.return_value = bdms
 | 
			
		||||
 | 
			
		||||
        with mock.patch.object(self.compute_api.compute_rpcapi,
 | 
			
		||||
        with mock.patch.object(self.compute_api.compute_task_api,
 | 
			
		||||
                'rebuild_instance') as rebuild_instance:
 | 
			
		||||
            self.compute_api.rebuild(self.context, instance, image_href,
 | 
			
		||||
                    admin_pass, files_to_inject)
 | 
			
		||||
@@ -1861,7 +1861,7 @@ class _ComputeAPIUnitTestMixIn(object):
 | 
			
		||||
                    injected_files=files_to_inject, image_ref=image_href,
 | 
			
		||||
                    orig_image_ref=image_href,
 | 
			
		||||
                    orig_sys_metadata=orig_system_metadata, bdms=bdms,
 | 
			
		||||
                    preserve_ephemeral=False, kwargs={})
 | 
			
		||||
                    preserve_ephemeral=False, host=instance.host, kwargs={})
 | 
			
		||||
 | 
			
		||||
        _check_auto_disk_config.assert_called_once_with(image=image)
 | 
			
		||||
        _checks_for_create_and_rebuild.assert_called_once_with(self.context,
 | 
			
		||||
@@ -1909,7 +1909,7 @@ class _ComputeAPIUnitTestMixIn(object):
 | 
			
		||||
        _get_image.side_effect = get_image
 | 
			
		||||
        bdm_get_by_instance_uuid.return_value = bdms
 | 
			
		||||
 | 
			
		||||
        with mock.patch.object(self.compute_api.compute_rpcapi,
 | 
			
		||||
        with mock.patch.object(self.compute_api.compute_task_api,
 | 
			
		||||
                'rebuild_instance') as rebuild_instance:
 | 
			
		||||
            self.compute_api.rebuild(self.context, instance, new_image_href,
 | 
			
		||||
                    admin_pass, files_to_inject)
 | 
			
		||||
@@ -1919,7 +1919,7 @@ class _ComputeAPIUnitTestMixIn(object):
 | 
			
		||||
                    injected_files=files_to_inject, image_ref=new_image_href,
 | 
			
		||||
                    orig_image_ref=orig_image_href,
 | 
			
		||||
                    orig_sys_metadata=orig_system_metadata, bdms=bdms,
 | 
			
		||||
                    preserve_ephemeral=False, kwargs={})
 | 
			
		||||
                    preserve_ephemeral=False, host=instance.host, kwargs={})
 | 
			
		||||
 | 
			
		||||
        _check_auto_disk_config.assert_called_once_with(image=new_image)
 | 
			
		||||
        _checks_for_create_and_rebuild.assert_called_once_with(self.context,
 | 
			
		||||
 
 | 
			
		||||
@@ -54,6 +54,7 @@ from nova.tests import fake_block_device
 | 
			
		||||
from nova.tests import fake_instance
 | 
			
		||||
from nova.tests import fake_notifier
 | 
			
		||||
from nova.tests import fake_server_actions
 | 
			
		||||
from nova.tests import fake_utils
 | 
			
		||||
from nova import utils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -87,6 +88,8 @@ class _BaseTestCase(object):
 | 
			
		||||
        self.stubs.Set(rpc.RequestContextSerializer, 'deserialize_context',
 | 
			
		||||
                       fake_deserialize_context)
 | 
			
		||||
 | 
			
		||||
        fake_utils.stub_out_utils_spawn_n(self.stubs)
 | 
			
		||||
 | 
			
		||||
    def _create_fake_instance(self, params=None, type_name='m1.tiny'):
 | 
			
		||||
        if not params:
 | 
			
		||||
            params = {}
 | 
			
		||||
@@ -1119,6 +1122,21 @@ class _BaseTaskTestCase(object):
 | 
			
		||||
        self.stubs.Set(rpc.RequestContextSerializer, 'deserialize_context',
 | 
			
		||||
                       fake_deserialize_context)
 | 
			
		||||
 | 
			
		||||
    def _prepare_rebuild_args(self, update_args=None):
 | 
			
		||||
        rebuild_args = {'new_pass': 'admin_password',
 | 
			
		||||
                        'injected_files': 'files_to_inject',
 | 
			
		||||
                        'image_ref': 'image_ref',
 | 
			
		||||
                        'orig_image_ref': 'orig_image_ref',
 | 
			
		||||
                        'orig_sys_metadata': 'orig_sys_meta',
 | 
			
		||||
                        'bdms': {},
 | 
			
		||||
                        'recreate': False,
 | 
			
		||||
                        'on_shared_storage': False,
 | 
			
		||||
                        'preserve_ephemeral': False,
 | 
			
		||||
                        'host': 'compute-host'}
 | 
			
		||||
        if update_args:
 | 
			
		||||
            rebuild_args.update(update_args)
 | 
			
		||||
        return rebuild_args
 | 
			
		||||
 | 
			
		||||
    def test_live_migrate(self):
 | 
			
		||||
        inst = fake_instance.fake_db_instance()
 | 
			
		||||
        inst_obj = objects.Instance._from_db_object(
 | 
			
		||||
@@ -1477,6 +1495,83 @@ class _BaseTaskTestCase(object):
 | 
			
		||||
        system_metadata['shelved_host'] = 'fake-mini'
 | 
			
		||||
        self.conductor_manager.unshelve_instance(self.context, instance)
 | 
			
		||||
 | 
			
		||||
    def test_rebuild_instance(self):
 | 
			
		||||
        db_instance = jsonutils.to_primitive(self._create_fake_instance())
 | 
			
		||||
        inst_obj = objects.Instance.get_by_uuid(self.context,
 | 
			
		||||
                                                db_instance['uuid'])
 | 
			
		||||
        rebuild_args = self._prepare_rebuild_args({'host': inst_obj.host})
 | 
			
		||||
 | 
			
		||||
        with contextlib.nested(
 | 
			
		||||
            mock.patch.object(self.conductor_manager.compute_rpcapi,
 | 
			
		||||
                              'rebuild_instance'),
 | 
			
		||||
            mock.patch.object(self.conductor_manager.scheduler_rpcapi,
 | 
			
		||||
                              'select_destinations')
 | 
			
		||||
        ) as (rebuild_mock, select_dest_mock):
 | 
			
		||||
            self.conductor_manager.rebuild_instance(context=self.context,
 | 
			
		||||
                                            instance=inst_obj,
 | 
			
		||||
                                            **rebuild_args)
 | 
			
		||||
            self.assertFalse(select_dest_mock.called)
 | 
			
		||||
            rebuild_mock.assert_called_once_with(self.context,
 | 
			
		||||
                               instance=inst_obj,
 | 
			
		||||
                               **rebuild_args)
 | 
			
		||||
 | 
			
		||||
    def test_rebuild_instance_with_scheduler(self):
 | 
			
		||||
        db_instance = jsonutils.to_primitive(self._create_fake_instance())
 | 
			
		||||
        inst_obj = objects.Instance.get_by_uuid(self.context,
 | 
			
		||||
                                                db_instance['uuid'])
 | 
			
		||||
        inst_obj.host = 'noselect'
 | 
			
		||||
        rebuild_args = self._prepare_rebuild_args({'host': None})
 | 
			
		||||
        expected_host = 'thebesthost'
 | 
			
		||||
        request_spec = {}
 | 
			
		||||
        filter_properties = {'ignore_hosts': [(inst_obj.host)]}
 | 
			
		||||
 | 
			
		||||
        with contextlib.nested(
 | 
			
		||||
            mock.patch.object(self.conductor_manager.compute_rpcapi,
 | 
			
		||||
                              'rebuild_instance'),
 | 
			
		||||
            mock.patch.object(self.conductor_manager.scheduler_rpcapi,
 | 
			
		||||
                              'select_destinations',
 | 
			
		||||
                              return_value=[{'host': expected_host}]),
 | 
			
		||||
            mock.patch('nova.scheduler.utils.build_request_spec',
 | 
			
		||||
                       return_value=request_spec)
 | 
			
		||||
        ) as (rebuild_mock, select_dest_mock, bs_mock):
 | 
			
		||||
            self.conductor_manager.rebuild_instance(context=self.context,
 | 
			
		||||
                                            instance=inst_obj,
 | 
			
		||||
                                            **rebuild_args)
 | 
			
		||||
            select_dest_mock.assert_called_once_with(self.context,
 | 
			
		||||
                                                     request_spec,
 | 
			
		||||
                                                     filter_properties)
 | 
			
		||||
            rebuild_args['host'] = expected_host
 | 
			
		||||
            rebuild_mock.assert_called_once_with(self.context,
 | 
			
		||||
                                            instance=inst_obj,
 | 
			
		||||
                                            **rebuild_args)
 | 
			
		||||
 | 
			
		||||
    def test_rebuild_instance_with_scheduler_no_host(self):
 | 
			
		||||
        db_instance = jsonutils.to_primitive(self._create_fake_instance())
 | 
			
		||||
        inst_obj = objects.Instance.get_by_uuid(self.context,
 | 
			
		||||
                                                db_instance['uuid'])
 | 
			
		||||
        inst_obj.host = 'noselect'
 | 
			
		||||
        rebuild_args = self._prepare_rebuild_args({'host': None})
 | 
			
		||||
        request_spec = {}
 | 
			
		||||
        filter_properties = {'ignore_hosts': [(inst_obj.host)]}
 | 
			
		||||
 | 
			
		||||
        with contextlib.nested(
 | 
			
		||||
            mock.patch.object(self.conductor_manager.compute_rpcapi,
 | 
			
		||||
                              'rebuild_instance'),
 | 
			
		||||
            mock.patch.object(self.conductor_manager.scheduler_rpcapi,
 | 
			
		||||
                              'select_destinations',
 | 
			
		||||
                              side_effect=exc.NoValidHost(reason='')),
 | 
			
		||||
            mock.patch('nova.scheduler.utils.build_request_spec',
 | 
			
		||||
                       return_value=request_spec)
 | 
			
		||||
        ) as (rebuild_mock, select_dest_mock, bs_mock):
 | 
			
		||||
            self.assertRaises(exc.NoValidHost,
 | 
			
		||||
                              self.conductor_manager.rebuild_instance,
 | 
			
		||||
                              context=self.context, instance=inst_obj,
 | 
			
		||||
                              **rebuild_args)
 | 
			
		||||
            select_dest_mock.assert_called_once_with(self.context,
 | 
			
		||||
                                                     request_spec,
 | 
			
		||||
                                                     filter_properties)
 | 
			
		||||
            self.assertFalse(rebuild_mock.called)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
 | 
			
		||||
    """ComputeTaskManager Tests."""
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user