Support rescuing an instance with shares

Allow to rescue an instance with shares attached.

Manila is the OpenStack Shared Filesystems service.
These series of patches implement changes required in Nova to allow the shares
provided by Manila to be associated with and attached to instances using
virtiofs.

Implements: blueprint libvirt-virtiofs-attach-manila-shares
Change-Id: I27a408b5af6103c60947fb2c4415cafe11f37f61
This commit is contained in:
René Ribaud
2022-10-04 16:59:06 +02:00
parent f8810b4347
commit 19bf42a582
11 changed files with 185 additions and 40 deletions

View File

@ -5038,6 +5038,8 @@ class ComputeManager(manager.Manager):
block_device_info = self._get_instance_block_device_info(
context, instance, bdms=bdms)
share_info = self._get_share_info(context, instance)
extra_usage_info = {'rescue_image_name':
self._get_image_name(rescue_image_meta)}
self._notify_about_instance_usage(context, instance,
@ -5050,9 +5052,11 @@ class ComputeManager(manager.Manager):
try:
self._power_off_instance(context, instance, clean_shutdown)
self._mount_all_shares(context, instance, share_info)
self.driver.rescue(context, instance, network_info,
rescue_image_meta, admin_password,
block_device_info)
block_device_info, share_info)
except Exception as e:
LOG.exception("Error trying to Rescue Instance",
instance=instance)

View File

@ -2417,7 +2417,7 @@ class ComputeTestCase(BaseTestCase,
'unrescued': False}
def fake_rescue(self, context, instance_ref, network_info, image_meta,
rescue_password, block_device_info):
rescue_password, block_device_info, share_info):
called['rescued'] = True
self.stub_out('nova.virt.fake.FakeDriver.rescue', fake_rescue)
@ -2448,7 +2448,7 @@ class ComputeTestCase(BaseTestCase,
def test_rescue_notifications(self, mock_context, mock_notify):
# Ensure notifications on instance rescue.
def fake_rescue(self, context, instance_ref, network_info, image_meta,
rescue_password, block_device_info):
rescue_password, block_device_info, share_info):
pass
self.stub_out('nova.virt.fake.FakeDriver.rescue', fake_rescue)
@ -2545,13 +2545,14 @@ class ComputeTestCase(BaseTestCase,
self.compute.terminate_instance(self.context, instance, [])
@mock.patch('nova.compute.manager.ComputeManager._get_share_info')
@mock.patch.object(nova.compute.manager.ComputeManager,
'_get_instance_block_device_info')
@mock.patch.object(fake.FakeDriver, 'power_off')
@mock.patch.object(fake.FakeDriver, 'rescue')
@mock.patch.object(compute_manager.ComputeManager, '_get_rescue_image')
def test_rescue_handle_err(self, mock_get, mock_rescue, mock_power_off,
mock_get_block_info):
mock_get_block_info, mock_get_share_info):
# If the driver fails to rescue, instance state should got to ERROR
# and the exception should be converted to InstanceNotRescuable
inst_obj = self._create_fake_instance_obj()
@ -2562,6 +2563,9 @@ class ComputeTestCase(BaseTestCase,
expected_message = ('Instance %s cannot be rescued: '
'Driver Error: Try again later' % inst_obj.uuid)
share_info = objects.ShareMappingList()
mock_get_share_info.return_value = share_info
with testtools.ExpectedException(
exception.InstanceNotRescuable, expected_message):
self.compute.rescue_instance(
@ -2573,18 +2577,25 @@ class ComputeTestCase(BaseTestCase,
mock_get.assert_called_once_with(mock.ANY, inst_obj, mock.ANY)
mock_rescue.assert_called_once_with(mock.ANY, inst_obj, [],
mock.ANY, 'password',
mock.sentinel.block_device_info)
mock.sentinel.block_device_info,
share_info)
@mock.patch.object(nova.virt.fake.FakeDriver, "mount_share")
@mock.patch('nova.compute.manager.ComputeManager._get_share_info')
@mock.patch.object(nova.compute.manager.ComputeManager,
'_get_instance_block_device_info')
@mock.patch.object(image_api.API, "get")
@mock.patch.object(fake.FakeDriver, 'power_off')
@mock.patch.object(nova.virt.fake.FakeDriver, "rescue")
def test_rescue_with_image_specified(self, mock_rescue, mock_power_off,
mock_image_get, mock_get_block_info):
mock_image_get, mock_get_block_info, mock_get_share_info,
mock_drv_mount):
image_ref = uuids.image_instance
rescue_image_meta = {}
params = {"task_state": task_states.RESCUING}
share_info = objects.ShareMappingList()
mock_get_share_info.return_value = share_info
instance = self._create_fake_instance_obj(params=params)
ctxt = context.get_admin_context()
@ -2599,24 +2610,30 @@ class ComputeTestCase(BaseTestCase,
clean_shutdown=True)
mock_image_get.assert_called_with(ctxt, image_ref)
mock_drv_mount.assert_not_called()
mock_rescue.assert_called_with(ctxt, instance, [],
test.MatchType(objects.ImageMeta),
'password',
mock.sentinel.block_device_info)
mock.sentinel.block_device_info,
share_info)
self.compute.terminate_instance(ctxt, instance, [])
@mock.patch.object(nova.virt.fake.FakeDriver, "mount_share")
@mock.patch('nova.compute.manager.ComputeManager._get_share_info')
@mock.patch.object(nova.compute.manager.ComputeManager,
'_get_instance_block_device_info')
@mock.patch.object(image_api.API, "get")
@mock.patch.object(fake.FakeDriver, 'power_off')
@mock.patch.object(nova.virt.fake.FakeDriver, "rescue")
def test_rescue_with_base_image_when_image_not_specified(self,
mock_rescue, mock_power_off, mock_image_get, mock_get_block_info):
image_ref = FAKE_IMAGE_REF
system_meta = {"image_base_image_ref": image_ref}
def test_rescue_with_image_specified_and_share(
self, mock_rescue, mock_power_off, mock_image_get, mock_get_block_info,
mock_get_share_info, mock_drv_mount):
image_ref = uuids.image_instance
rescue_image_meta = {}
params = {"task_state": task_states.RESCUING,
"system_metadata": system_meta}
params = {"task_state": task_states.RESCUING}
share_info = self.fake_share_info()
mock_get_share_info.return_value = share_info
instance = self._create_fake_instance_obj(params=params)
ctxt = context.get_admin_context()
@ -2626,6 +2643,99 @@ class ComputeTestCase(BaseTestCase,
mock_get_block_info.return_value = mock.sentinel.block_device_info
mock_image_get.return_value = rescue_image_meta
mock_get_share_info.return_value = share_info
self.compute.rescue_instance(mock_context, instance=instance,
rescue_password="password", rescue_image_ref=image_ref,
clean_shutdown=True)
mock_image_get.assert_called_with(ctxt, image_ref)
mock_drv_mount.assert_called_with(ctxt, instance, share_info[0])
mock_rescue.assert_called_with(
ctxt,
instance,
[],
test.MatchType(objects.ImageMeta),
"password",
mock.sentinel.block_device_info,
share_info,
)
self.compute.terminate_instance(ctxt, instance, [])
@mock.patch('nova.objects.instance_fault.InstanceFault.create')
@mock.patch.object(nova.virt.fake.FakeDriver, "mount_share")
@mock.patch('nova.compute.manager.ComputeManager._get_share_info')
@mock.patch.object(nova.compute.manager.ComputeManager,
'_get_instance_block_device_info')
@mock.patch.object(image_api.API, "get")
@mock.patch.object(fake.FakeDriver, 'power_off')
@mock.patch.object(nova.virt.fake.FakeDriver, "rescue")
def test_rescue_with_image_specified_and_mount_error(
self, mock_rescue, mock_power_off, mock_image_get, mock_get_block_info,
mock_get_share_info, mock_drv_mount, mock_db_fault):
image_ref = uuids.image_instance
rescue_image_meta = {}
params = {"task_state": task_states.RESCUING}
share_info = self.fake_share_info()
mock_get_share_info.return_value = share_info
instance = self._create_fake_instance_obj(params=params)
ctxt = context.get_admin_context()
mock_context = mock.Mock()
mock_context.elevated.return_value = ctxt
mock_get_block_info.return_value = mock.sentinel.block_device_info
mock_image_get.return_value = rescue_image_meta
mock_drv_mount.side_effect = exception.ShareMountError(
share_id=share_info[0].share_id,
server_id=instance.uuid,
reason="fake_reason",
)
self.assertRaises(
exception.InstanceNotRescuable,
self.compute.rescue_instance,
mock_context,
instance=instance,
rescue_password="password",
rescue_image_ref=image_ref,
clean_shutdown=True,
)
self.assertEqual(instance.vm_state, 'error')
self.compute.terminate_instance(ctxt, instance, [])
@mock.patch('nova.compute.manager.ComputeManager._get_share_info')
@mock.patch.object(nova.compute.manager.ComputeManager,
'_get_instance_block_device_info')
@mock.patch.object(image_api.API, "get")
@mock.patch.object(fake.FakeDriver, 'power_off')
@mock.patch.object(nova.virt.fake.FakeDriver, "rescue")
def test_rescue_with_base_image_when_image_not_specified(self,
mock_rescue, mock_power_off, mock_image_get, mock_get_block_info,
mock_get_share_info):
image_ref = FAKE_IMAGE_REF
system_meta = {"image_base_image_ref": image_ref}
rescue_image_meta = {}
params = {"task_state": task_states.RESCUING,
"system_metadata": system_meta}
share_info = objects.ShareMappingList()
mock_get_share_info.return_value = share_info
instance = self._create_fake_instance_obj(params=params)
ctxt = context.get_admin_context()
mock_context = mock.Mock()
mock_context.elevated.return_value = ctxt
mock_get_block_info.return_value = mock.sentinel.block_device_info
mock_image_get.return_value = rescue_image_meta
share_info = objects.ShareMappingList()
mock_get_share_info.return_value = share_info
self.compute.rescue_instance(mock_context, instance=instance,
rescue_password="password",
rescue_image_ref=None,
@ -2636,7 +2746,8 @@ class ComputeTestCase(BaseTestCase,
mock_rescue.assert_called_with(ctxt, instance, [],
test.MatchType(objects.ImageMeta),
'password',
mock.sentinel.block_device_info)
mock.sentinel.block_device_info,
share_info)
self.compute.terminate_instance(self.context, instance, [])
def test_power_on(self):

View File

@ -5706,6 +5706,7 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase,
fake_nw_info = network_model.NetworkInfo()
rescue_image_meta = objects.ImageMeta.from_dict(
{'id': uuids.image_id, 'name': uuids.image_name})
with test.nested(
mock.patch.object(self.context, 'elevated',
return_value=self.context),
@ -5724,13 +5725,18 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase,
mock.patch.object(compute_utils, 'notify_usage_exists'),
mock.patch.object(self.compute, '_get_power_state',
return_value=power_state.RUNNING),
mock.patch.object(instance, 'save')
mock.patch.object(instance, 'save'),
mock.patch('nova.compute.manager.ComputeManager._get_share_info')
) as (
elevated_context, get_nw_info, get_rescue_image,
get_bdm_list, get_block_info, notify_instance_usage,
power_off_instance, driver_rescue, notify_usage_exists,
get_power_state, instance_save
get_power_state, instance_save, mock_get_share_info
):
share_info = objects.ShareMappingList()
mock_get_share_info.return_value = share_info
self.compute.rescue_instance(
self.context, instance, rescue_password='verybadpass',
rescue_image_ref=None, clean_shutdown=clean_shutdown)
@ -5765,7 +5771,7 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase,
driver_rescue.assert_called_once_with(
self.context, instance, fake_nw_info, rescue_image_meta,
'verybadpass', mock.sentinel.block_device_info)
'verybadpass', mock.sentinel.block_device_info, share_info)
notify_usage_exists.assert_called_once_with(self.compute.notifier,
self.context, instance, 'fake-mini', current_period=True)

View File

@ -2645,9 +2645,11 @@ class IronicDriverSyncTestCase(IronicDriverTestCase):
fake_looping_call = FakeLoopingCall()
mock_looping.return_value = fake_looping_call
share_info = objects.ShareMappingList()
instance = fake_instance.fake_instance_obj(self.ctx, node=node.id)
self.driver.rescue(self.ctx, instance, None, None, 'xyz', None)
self.driver.rescue(self.ctx, instance, None, None, 'xyz', None,
share_info)
self.mock_conn.set_node_provision_state.assert_called_once_with(
node.id, 'rescue', rescue_password='xyz',
)
@ -2660,12 +2662,13 @@ class IronicDriverSyncTestCase(IronicDriverTestCase):
mock_looping.return_value = fake_looping_call
self.mock_conn.set_node_provision_state.side_effect = \
sdk_exc.BadRequestException()
share_info = objects.ShareMappingList()
instance = fake_instance.fake_instance_obj(self.ctx, node=node.id)
self.assertRaises(
exception.InstanceRescueFailure,
self.driver.rescue,
self.ctx, instance, None, None, 'xyz', None,
self.ctx, instance, None, None, 'xyz', None, share_info
)
self.mock_conn.set_node_provision_state.assert_called_once_with(
node.id, 'rescue', rescue_password='xyz',
@ -2680,11 +2683,12 @@ class IronicDriverSyncTestCase(IronicDriverTestCase):
fake_validate.side_effect = exception.InstanceNotFound(
instance_id='fake',
)
share_info = objects.ShareMappingList()
self.assertRaises(
exception.InstanceRescueFailure,
self.driver.rescue,
self.ctx, instance, None, None, 'xyz', None,
self.ctx, instance, None, None, 'xyz', None, share_info
)
@mock.patch.object(ironic_driver.IronicDriver,
@ -2695,12 +2699,13 @@ class IronicDriverSyncTestCase(IronicDriverTestCase):
last_error='rescue failed')
fake_validate.return_value = node
share_info = objects.ShareMappingList()
instance = fake_instance.fake_instance_obj(self.ctx, node=node.id)
self.assertRaises(
exception.InstanceRescueFailure,
self.driver.rescue,
self.ctx, instance, None, None, 'xyz', None,
self.ctx, instance, None, None, 'xyz', None, share_info
)
@mock.patch.object(loopingcall, 'FixedIntervalLoopingCall')

View File

@ -26217,7 +26217,7 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
self, instance, mock_instance_metadata, mock_supports_direct_io,
mock_build_device_metadata, mock_set_host_enabled, mock_get_mdev,
mock_get_image_meta_by_ref, image_meta_dict=None, exists=None,
instance_image_meta_dict=None, block_device_info=None,
instance_image_meta_dict=None, block_device_info=None, share_info=None
):
self.flags(instances_path=self.useFixture(fixtures.TempDir()).path)
@ -26256,9 +26256,11 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
mock.patch.object(self.drvr, '_connect_volume'),
) as (mock_create_guest, mock_connect_volume):
share_info = objects.ShareMappingList()
self.drvr.rescue(self.context, instance,
network_info, image_meta, rescue_password,
block_device_info)
block_device_info, share_info)
self.assertTrue(mock_create_guest.called)
@ -26388,9 +26390,10 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
# Assert that InstanceNotRescuable is raised for lxc virt_type
self.flags(virt_type='lxc', group='libvirt')
share_info = objects.ShareMappingList()
self.assertRaises(exception.InstanceNotRescuable, self.drvr.rescue,
self.context, instance, network_info,
rescue_image_meta, None, None)
rescue_image_meta, None, None, share_info)
def test_rescue_stable_device(self):
# Assert the imagebackend behaviour and domain device layout
@ -26462,12 +26465,15 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
'block_device_mapping': bdms}
bdm = block_device_info['block_device_mapping'][0]
bdm['connection_info'] = conn_info
share_info = objects.ShareMappingList()
backend, domain = self._test_rescue(
instance,
image_meta_dict=rescue_image_meta_dict,
instance_image_meta_dict=inst_image_meta_dict,
block_device_info=block_device_info)
instance,
image_meta_dict=rescue_image_meta_dict,
instance_image_meta_dict=inst_image_meta_dict,
block_device_info=block_device_info,
share_info=share_info,
)
# Assert that we created the expected set of disks, and no others
self.assertEqual(['disk.rescue', 'kernel.rescue', 'ramdisk.rescue'],
@ -26536,16 +26542,18 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
) as (
mock_create, mock_destroy, mock_get_guest_xml, mock_create_image,
mock_get_existing_xml, mock_inst_path, mock_get_disk_info,
mock_image_get, mock_from_dict, mock_open,
mock_image_get, mock_from_dict, mock_open
):
self.flags(virt_type='kvm', group='libvirt')
mock_image_get.return_value = mock.sentinel.bdm_image_meta_dict
mock_from_dict.return_value = mock.sentinel.bdm_image_meta
mock_get_disk_info.return_value = disk_info
share_info = nova.objects.share_mapping.ShareMappingList()
drvr.rescue(self.context, instance, network_info,
rescue_image_meta, mock.sentinel.rescue_password,
block_device_info)
block_device_info, share_info=share_info)
# Assert that we fetch image metadata from Glance using the image
# uuid stashed in the BDM and build an image_meta object using the
@ -26565,7 +26573,7 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
mock_get_guest_xml.assert_called_once_with(
self.context, instance, network_info, disk_info,
mock.sentinel.bdm_image_meta, rescue=mock.ANY, mdevs=mock.ANY,
block_device_info=block_device_info)
block_device_info=block_device_info, share_info=share_info)
def test_rescue_stable_device_bfv(self):
"""Assert the disk layout when rescuing BFV instances"""

View File

@ -284,8 +284,9 @@ class _VirtDriverTestCase(_FakeDriverBackendTestCase):
def test_rescue(self):
image_meta = objects.ImageMeta.from_dict({})
instance_ref, network_info = self._get_running_instance()
share_info = objects.ShareMappingList()
self.connection.rescue(self.ctxt, instance_ref, network_info,
image_meta, '', None)
image_meta, '', None, share_info)
@catch_notimplementederror
def test_unrescue_unrescued_instance(self):
@ -297,8 +298,9 @@ class _VirtDriverTestCase(_FakeDriverBackendTestCase):
def test_unrescue_rescued_instance(self, mock_unlink):
image_meta = objects.ImageMeta.from_dict({})
instance_ref, network_info = self._get_running_instance()
share_info = objects.ShareMappingList()
self.connection.rescue(self.ctxt, instance_ref, network_info,
image_meta, '', None)
image_meta, '', None, share_info)
self.connection.unrescue(self.ctxt, instance_ref)
@catch_notimplementederror

View File

@ -1016,7 +1016,7 @@ class ComputeDriver(object):
raise NotImplementedError()
def rescue(self, context, instance, network_info, image_meta,
rescue_password, block_device_info):
rescue_password, block_device_info, share_info):
"""Rescue the specified instance.
:param nova.context.RequestContext context:
@ -1030,6 +1030,8 @@ class ComputeDriver(object):
:param rescue_password: new root password to set for rescue.
:param dict block_device_info:
The block device mapping of the instance.
:param nova.objects.share_mapping.ShareMapingList share_info
list of share_mapping
"""
raise NotImplementedError()

View File

@ -245,7 +245,7 @@ class FakeDriver(driver.ComputeDriver):
pass
def rescue(self, context, instance, network_info, image_meta,
rescue_password, block_device_info):
rescue_password, block_device_info, share_info):
pass
def unrescue(

View File

@ -2150,7 +2150,7 @@ class IronicDriver(virt_driver.ComputeDriver):
raise exception.IronicAPIVersionNotAvailable(version=version)
def rescue(self, context, instance, network_info, image_meta,
rescue_password, block_device_info):
rescue_password, block_device_info, share_info):
"""Rescue the specified instance.
:param nova.context.RequestContext context:
@ -2165,6 +2165,8 @@ class IronicDriver(virt_driver.ComputeDriver):
:param rescue_password: new root password to set for rescue.
:param dict block_device_info:
The block device mapping of the instance.
:param nova.objects.share_mapping.ShareMapingList share_info
optional list of share_mapping
:raise InstanceRescueFailure if rescue fails.
"""
LOG.debug('Rescue called for instance', instance=instance)

View File

@ -4465,7 +4465,7 @@ class LibvirtDriver(driver.ComputeDriver):
)
def rescue(self, context, instance, network_info, image_meta,
rescue_password, block_device_info):
rescue_password, block_device_info, share_info):
"""Loads a VM using rescue images.
A rescue is normally performed when something goes wrong with the
@ -4496,9 +4496,13 @@ class LibvirtDriver(driver.ComputeDriver):
:param rescue_password: new root password to set for rescue.
:param dict block_device_info:
The block device mapping of the instance.
:param nova.objects.ShareMappingList share_info:
list of share_mapping
"""
instance_dir = libvirt_utils.get_instance_path(instance)
unrescue_xml = self._get_existing_domain_xml(instance, network_info)
unrescue_xml = self._get_existing_domain_xml(
instance, network_info, share_info=share_info)
unrescue_xml_path = os.path.join(instance_dir, 'unrescue.xml')
with open(unrescue_xml_path, 'w') as f:
f.write(unrescue_xml)
@ -4584,7 +4588,8 @@ class LibvirtDriver(driver.ComputeDriver):
xml = self._get_guest_xml(context, instance, network_info, disk_info,
image_meta, rescue=rescue_images,
mdevs=mdevs,
block_device_info=block_device_info)
block_device_info=block_device_info,
share_info=share_info)
self._destroy(instance)
self._create_guest(
context, xml, instance, post_xml_callback=gen_confdrive,

View File

@ -656,7 +656,7 @@ class VMwareVCDriver(driver.ComputeDriver):
self._vmops.resume(instance)
def rescue(self, context, instance, network_info, image_meta,
rescue_password, block_device_info):
rescue_password, block_device_info, share_info):
"""Rescue the specified instance."""
self._vmops.rescue(context, instance, network_info, image_meta)