Move rebuild to conductor and add find host logic
[*] Move rebuild instance to conductor task api will help
in consolidating the logic for both evacuate instance and
rebuild instance. Rebuild instance and evacuate instance
uses the conductor task api for rebuild instance workflow.
[*] Add logic required to support rebuild without host.
(used by evacuate)
[*] Refactor rebuild action to pass the instance host as argument,
instead of None.
Evacuate and Rebuild will behave the same way as today.
Modifications for the new feature in API will be implemented in
new patches.
Partial Implements: blueprint find-host-and-evacuate-instance
Change-Id: I4b1a9c2227574db7179441d531a49145f213ea87
Co-Authored-By: Juan M. Olle <juan.m.olle@intel.com>
Co-Authored-By: Anuj Mathur <anujm@thoughtworks.com>
Co-Authored-By: Navneet Kumar <navneetk@thoughtworks.com>
Co-Authored-By: Claxton Correya <claxton@gmail.com>
This commit is contained in:
@@ -2173,11 +2173,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
|
||||
@@ -3011,6 +3012,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
|
||||
@@ -3025,17 +3032,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)
|
||||
|
||||
@@ -7178,7 +7178,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'
|
||||
@@ -9151,7 +9151,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,
|
||||
|
||||
@@ -1795,7 +1795,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)
|
||||
@@ -1805,7 +1805,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,
|
||||
@@ -1853,7 +1853,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)
|
||||
@@ -1863,7 +1863,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,
|
||||
|
||||
@@ -51,6 +51,7 @@ from nova.tests.compute import test_compute
|
||||
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
|
||||
|
||||
|
||||
@@ -84,6 +85,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 = {}
|
||||
@@ -1115,6 +1118,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(
|
||||
@@ -1469,6 +1487,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