Permalink
Browse files

Allow user to specify image to use during rescue

This commit includes the compute changes to accept a parameter
rescue_image_ref in the rescue_instance method.
If specified, this image_ref will be used to rescue the instance.
If the image is not specified, then the base image ref is used by default.

Partially Implements: blueprint allow-image-to-be-specified-during-rescue

DocImpact

Change-Id: I59311fcfcb8eb54701618e92e5cdada109235b38
  • Loading branch information...
aditiraveesh committed Mar 5, 2014
1 parent 3e66aad commit e88fc374c6868602c83ac688fd5d6c2709b06a3c
View
@@ -2571,7 +2571,8 @@ def resume(self, context, instance):
@check_instance_lock
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED,
vm_states.ERROR])
def rescue(self, context, instance, rescue_password=None):
def rescue(self, context, instance, rescue_password=None,
rescue_image_ref=None):
"""Rescue the given instance."""
bdms = block_device_obj.BlockDeviceMappingList.get_by_instance_uuid(
@@ -2593,7 +2594,7 @@ def rescue(self, context, instance, rescue_password=None):
self._record_action_start(context, instance, instance_actions.RESCUE)
self.compute_rpcapi.rescue_instance(context, instance=instance,
rescue_password=rescue_password)
rescue_password=rescue_password, rescue_image_ref=rescue_image_ref)
@wrap_check_policy
@check_instance_lock
@@ -303,12 +303,15 @@ def get_diagnostics(self, context, instance):
return self._call_to_cells(context, instance, 'get_diagnostics')
@check_instance_cell
def rescue(self, context, instance, rescue_password=None):
def rescue(self, context, instance, rescue_password=None,
rescue_image_ref=None):
"""Rescue the given instance."""
super(ComputeCellsAPI, self).rescue(context, instance,
rescue_password=rescue_password)
rescue_password=rescue_password,
rescue_image_ref=rescue_image_ref)
self._cast_to_cells(context, instance, 'rescue',
rescue_password=rescue_password)
rescue_password=rescue_password,
rescue_image_ref=rescue_image_ref)
@check_instance_cell
def unrescue(self, context, instance):
View
@@ -571,7 +571,7 @@ def wait_for_instance_event(self, instance, event_names, deadline=300,
class ComputeManager(manager.Manager):
"""Manages the running instances from creation to destruction."""
target = messaging.Target(version='3.23')
target = messaging.Target(version='3.24')
def __init__(self, compute_driver=None, *args, **kwargs):
"""Load configuration options and connect to the hypervisor."""
@@ -2933,20 +2933,20 @@ def inject_file(self, context, path, file_contents, instance):
instance=instance)
self.driver.inject_file(instance, path, file_contents)
def _get_rescue_image(self, context, instance):
def _get_rescue_image(self, context, instance, rescue_image_ref=None):
"""Determine what image should be used to boot the rescue VM."""
system_meta = utils.instance_sys_meta(instance)
rescue_image_ref = system_meta.get('image_base_image_ref')
# 1. If rescue_image_ref is passed in, use that for rescue.
# 2. Else, use the base image associated with instance's current image.
# The idea here is to provide the customer with a rescue
# environment which they are familiar with.
# So, if they built their instance off of a Debian image,
# their rescue VM will also be Debian.
# 3. As a last resort, use instance's current image.
if not rescue_image_ref:
system_meta = utils.instance_sys_meta(instance)
rescue_image_ref = system_meta.get('image_base_image_ref')
# 1. First try to use base image associated with instance's current
# image.
#
# The idea here is to provide the customer with a rescue environment
# which they are familiar with. So, if they built their instance off of
# a Debian image, their rescue VM will also be Debian.
if not rescue_image_ref:
# 2. As a last resort, use instance's current image
LOG.warn(_('Unable to find a different image to use for rescue VM,'
' using instance\'s current image'))
rescue_image_ref = instance['image_ref']
@@ -2964,10 +2964,9 @@ def _get_rescue_image(self, context, instance):
@wrap_exception()
@reverts_task_state
@wrap_instance_event
def rescue_instance(self, context, instance, rescue_password):
"""Rescue an instance on this host.
:param rescue_password: password to set on rescue instance
"""
def rescue_instance(self, context, instance, rescue_password,
rescue_image_ref=None):
"""Rescue an instance on this host."""
context = context.elevated()
LOG.audit(_('Rescuing'), context=context, instance=instance)
@@ -2976,7 +2975,8 @@ def rescue_instance(self, context, instance, rescue_password):
network_info = self._get_instance_nw_info(context, instance)
rescue_image_meta = self._get_rescue_image(context, instance)
rescue_image_meta = self._get_rescue_image(context, instance,
rescue_image_ref)
extra_usage_info = {'rescue_image_name':
rescue_image_meta.get('name', '')}
View
@@ -241,6 +241,7 @@ class ComputeAPI(object):
3.21 - Made rebuild take new-world BDM objects
3.22 - Made terminate_instance take new-world BDM objects
3.23 - Added external_instance_event()
3.24 - Update rescue_instance() to take optional rescue_image_ref
'''
VERSION_ALIASES = {
@@ -686,17 +687,21 @@ def remove_volume_connection(self, ctxt, instance, volume_id, host):
return cctxt.call(ctxt, 'remove_volume_connection',
instance=instance_p, volume_id=volume_id)
def rescue_instance(self, ctxt, instance, rescue_password):
if self.client.can_send_version('3.9'):
def rescue_instance(self, ctxt, instance, rescue_password,
rescue_image_ref=None):
instance = jsonutils.to_primitive(instance)
msg_args = {'rescue_password': rescue_password, 'instance': instance}
if self.client.can_send_version('3.24'):
version = '3.24'
msg_args['rescue_image_ref'] = rescue_image_ref
elif self.client.can_send_version('3.9'):
version = '3.9'
else:
# NOTE(russellb) Havana compat
version = self._get_compat_version('3.0', '2.44')
instance = jsonutils.to_primitive(instance)
cctxt = self.client.prepare(server=_compute_host(None, instance),
version=version)
cctxt.cast(ctxt, 'rescue_instance', instance=instance,
rescue_password=rescue_password)
cctxt.cast(ctxt, 'rescue_instance', **msg_args)
def reset_network(self, ctxt, instance):
# NOTE(russellb) Havana compat
@@ -1997,7 +1997,7 @@ def test_rescue_handle_err(self):
self.mox.StubOutWithMock(nova.virt.fake.FakeDriver, 'rescue')
self.compute._get_rescue_image(
mox.IgnoreArg(), inst_obj).AndReturn({})
mox.IgnoreArg(), inst_obj, mox.IgnoreArg()).AndReturn({})
nova.virt.fake.FakeDriver.rescue(
mox.IgnoreArg(), inst_obj, [], mox.IgnoreArg(), 'password'
).AndRaise(RuntimeError("Try again later"))
@@ -2016,6 +2016,75 @@ def test_rescue_handle_err(self):
self.assertEqual('some_random_state', inst_obj['vm_state'])
@mock.patch.object(nova.compute.utils, "get_image_metadata")
@mock.patch.object(nova.image.glance, "get_remote_image_service")
@mock.patch.object(nova.virt.fake.FakeDriver, "rescue")
def test_rescue_with_image_specified(self, mock_rescue,
mock_get_remote_image_service, mock_get_image_metadata):
image_ref = "image-ref"
rescue_image_meta = {}
params = {"task_state": task_states.RESCUING}
instance = self._create_fake_instance(params=params)
instance = self._objectify(instance)
ctxt = context.get_admin_context()
mock_context = mock.Mock()
mock_context.elevated.return_value = ctxt
mock_get_image_metadata.return_value = rescue_image_meta
mock_image_service = "image_service"
mock_get_remote_image_service.return_value = (mock_image_service, "id")
self.compute.rescue_instance(mock_context, instance=instance,
rescue_password="password", rescue_image_ref=image_ref)
mock_get_remote_image_service.assert_called_with(ctxt,
image_ref)
mock_get_image_metadata.assert_called_with(ctxt,
mock_image_service,
image_ref, instance)
mock_rescue.assert_called_with(ctxt, instance, [],
rescue_image_meta, 'password')
self.compute.terminate_instance(ctxt,
self._objectify(instance), [], [])
@mock.patch.object(nova.compute.utils, "get_image_metadata")
@mock.patch.object(nova.image.glance, "get_remote_image_service")
@mock.patch.object(nova.virt.fake.FakeDriver, "rescue")
def test_rescue_with_base_image_when_image_not_specified(self,
mock_rescue, mock_get_remote_image_service,
mock_get_image_metadata):
image_ref = "image-ref"
system_meta = {"image_base_image_ref": image_ref}
rescue_image_meta = {}
params = {"task_state": task_states.RESCUING,
"system_metadata": system_meta}
instance = self._create_fake_instance(params=params)
instance = self._objectify(instance)
ctxt = context.get_admin_context()
mock_context = mock.Mock()
mock_context.elevated.return_value = ctxt
mock_get_image_metadata.return_value = rescue_image_meta
mock_image_service = "image_service"
mock_get_remote_image_service.return_value = (mock_image_service, "id")
self.compute.rescue_instance(mock_context, instance=instance,
rescue_password="password")
mock_get_remote_image_service.assert_called_with(ctxt,
image_ref)
mock_get_image_metadata.assert_called_with(ctxt,
mock_image_service,
image_ref, instance)
mock_rescue.assert_called_with(ctxt, instance, [],
rescue_image_meta, 'password')
self.compute.terminate_instance(self.context,
self._objectify(instance), [], [])
def test_power_on(self):
# Ensure instance can be powered on.
@@ -567,15 +567,19 @@ def test_remove_volume_connection(self):
version='2.0')
def test_rescue_instance(self):
self.flags(compute='3.9', group='upgrade_levels')
self._test_compute_api('rescue_instance', 'cast',
instance=self.fake_instance, rescue_password='pw',
version='3.9')
instance=self.fake_instance, rescue_password='pw', version='3.9')
# NOTE(russellb) Havana compat
def test_rescue_instance_with_rescue_image_ref_passed(self):
self._test_compute_api('rescue_instance', 'cast',
instance=self.fake_instance, rescue_password='pw',
rescue_image_ref='fake_image_ref', version='3.24')
def test_rescue_instance_for_havana_compatibiltiy(self):
self.flags(compute='havana', group='upgrade_levels')
self._test_compute_api('rescue_instance', 'cast',
instance=self.fake_instance, rescue_password='pw',
version='2.44')
instance=self.fake_instance, rescue_password='pw', version='2.44')
def test_reset_network(self):
self._test_compute_api('reset_network', 'cast',
@@ -115,7 +115,8 @@
libvirt_opts = [
cfg.StrOpt('rescue_image_id',
help='Rescue ami image',
help='Rescue ami image. This will not be used if an image id '
'is provided by the user.',
deprecated_group='DEFAULT'),
cfg.StrOpt('rescue_kernel_id',
help='Rescue aki image',
@@ -2180,8 +2181,14 @@ def rescue(self, context, instance, network_info, image_meta,
unrescue_xml_path = os.path.join(instance_dir, 'unrescue.xml')
libvirt_utils.write_to_file(unrescue_xml_path, unrescue_xml)
if image_meta is not None:
rescue_image_id = image_meta.get('id')
else:
rescue_image_id = None
rescue_images = {
'image_id': CONF.libvirt.rescue_image_id or instance['image_ref'],
'image_id': (rescue_image_id or
CONF.libvirt.rescue_image_id or instance['image_ref']),
'kernel_id': (CONF.libvirt.rescue_kernel_id or
instance['kernel_id']),
'ramdisk_id': (CONF.libvirt.rescue_ramdisk_id or

0 comments on commit e88fc37

Please sign in to comment.