From 361ee2c69351cee2a580e6736b6be7b447369a6b Mon Sep 17 00:00:00 2001 From: Lee Yarwood Date: Tue, 7 Jan 2020 17:52:54 +0000 Subject: [PATCH] libvirt: Support boot from volume stable device instance rescue This change introduces support for boot from volume stable device instance rescue to the Libvirt driver by reporting the supports_bfv_rescue capability. The only change to the rescue method within the Libvirt driver is the introduction of a call to utils.get_bdm_image_metadata when we fail to lookup the image metadata of an instance. This function allowing us to extract image metadata if any is present from the provided block_device_mapping. Implements: blueprint virt-bfv-instance-rescue Change-Id: I99194539bd77790896a79fee47978905cba99bee --- nova/tests/functional/integrated_helpers.py | 1 + nova/tests/unit/virt/libvirt/test_driver.py | 135 ++++++++++++++++++++ nova/virt/libvirt/driver.py | 10 +- 3 files changed, 145 insertions(+), 1 deletion(-) diff --git a/nova/tests/functional/integrated_helpers.py b/nova/tests/functional/integrated_helpers.py index 4215e59df1c3..fb25db3c6688 100644 --- a/nova/tests/functional/integrated_helpers.py +++ b/nova/tests/functional/integrated_helpers.py @@ -460,6 +460,7 @@ class ProviderUsageBaseTestCase(test.TestCase, InstanceHelperMixin): os_traits.COMPUTE_IMAGE_TYPE_ISO, os_traits.COMPUTE_IMAGE_TYPE_QCOW2, os_traits.COMPUTE_IMAGE_TYPE_RAW, + os_traits.COMPUTE_RESCUE_BFV, ] ]) diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index a7190a3f3d56..c34debd9c33f 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -22792,6 +22792,141 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin): query = "devices/disk[source/@file = '%s']/boot/@order" % disk_path self.assertEqual('1', domain.xpath(query)[0]) + def test_supports_bfv_rescue_capability(self): + """Assert that the supports_bfv_rescue capability is set""" + drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) + self.assertTrue(drvr.capabilities.get('supports_bfv_rescue')) + + def test_rescue_stable_device_bfv_without_instance_image_ref(self): + """Assert that image_meta is fetched from the bdms for bfv instances""" + drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) + + # Set instance.image_ref to None for this BFV instance + instance = self._create_instance({'config_drive': str(True)}) + instance.image_ref = None + + rescue_image_meta = objects.ImageMeta.from_dict( + {'id': uuids.rescue_image_id, + 'name': 'rescue', + 'properties': {'hw_rescue_device': 'disk', + 'hw_rescue_bus': 'virtio'}}) + bdm = objects.BlockDeviceMapping(self.context, + **fake_block_device.FakeDbBlockDeviceDict({ + 'id': 1, + 'image_id': uuids.bdm_image_id, + 'source_type': 'image', + 'destination_type': 'volume', + 'device_name': '/dev/vda', + 'boot_index': 0})) + bdms = driver_block_device.convert_images([bdm]) + block_device_info = {'root_device_name': '/dev/vda', + 'ephemerals': [], + 'swap': None, + 'block_device_mapping': bdms} + network_info = _fake_network_info(self) + disk_info = {'mapping': {}} + + with test.nested( + mock.patch.object(drvr, '_create_domain'), + mock.patch.object(drvr, '_destroy'), + mock.patch.object(drvr, '_get_guest_xml'), + mock.patch.object(drvr, '_create_image'), + mock.patch.object(drvr, '_get_existing_domain_xml'), + mock.patch.object(libvirt_utils, 'write_to_file'), + mock.patch.object(libvirt_utils, 'get_instance_path'), + mock.patch('nova.virt.libvirt.blockinfo.get_disk_info'), + mock.patch('nova.image.glance.API.get'), + mock.patch('nova.objects.image_meta.ImageMeta.from_dict') + ) as (mock_create, mock_destroy, mock_get_guest_xml, mock_create_image, + mock_get_existing_xml, mock_write, mock_inst_path, + mock_get_disk_info, mock_image_get, mock_from_dict): + + 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 + + drvr.rescue(self.context, instance, network_info, + rescue_image_meta, mock.sentinel.rescue_password, + block_device_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 + # returned dict. + mock_image_get.assert_called_once_with( + self.context, uuids.bdm_image_id) + mock_from_dict.assert_called_once_with( + mock.sentinel.bdm_image_meta_dict) + + # Assert that get_disk_info is then called using this object + mock_get_disk_info.assert_called_once_with( + 'kvm', instance, mock.sentinel.bdm_image_meta, rescue=True, + block_device_info=block_device_info, + rescue_image_meta=rescue_image_meta) + + # Assert that this object is also used when building guest XML + 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) + + def test_rescue_stable_device_bfv(self): + """Assert the disk layout when rescuing BFV instances""" + + # NOTE(lyarwood): instance.image_ref is left in place here to allow us + # to reuse the _test_rescue test method as we only care about the + # eventual disk layout and not how we get the image_meta in this test. + instance = self._create_instance({'config_drive': str(True)}) + + # Set ephemeral_gb to 0 to avoid any disk.local disks for being used + instance.ephemeral_gb = 0 + inst_image_meta_dict = {'id': uuids.image_id, 'name': 'fake'} + rescue_image_meta_dict = { + 'id': uuids.rescue_image_id, + 'name': 'rescue', + 'properties': {'hw_rescue_device': 'disk', + 'hw_rescue_bus': 'virtio'}} + conn_info = { + 'driver_volume_type': 'iscsi', + 'data': {'device_path': '/dev/sdb'}} + bdm = objects.BlockDeviceMapping( + self.context, + **fake_block_device.FakeDbBlockDeviceDict({ + 'id': 1, + 'source_type': 'volume', + 'destination_type': 'volume', + 'device_name': '/dev/vda'})) + bdms = driver_block_device.convert_volumes([bdm]) + block_device_info = {'root_device_name': '/dev/vda', + 'ephemerals': [], + 'swap': None, + 'block_device_mapping': bdms} + bdm = block_device_info['block_device_mapping'][0] + bdm['connection_info'] = conn_info + + 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) + + # Assert that we created the expected set of disks, and no others + self.assertEqual(['disk.rescue', 'kernel.rescue', 'ramdisk.rescue'], + sorted(backend.created_disks.keys())) + + # Assert that the original disks are presented first with the rescue + # disk attached as the final device in the domain. + expected_disk_paths = [backend.disks['disk.config'].path, + '/dev/sdb', backend.disks['disk.rescue'].path] + query = 'devices/disk/source/@*[name()="file" or name()="dev"]' + disk_paths = domain.xpath(query) + self.assertEqual(expected_disk_paths, disk_paths) + + # Assert that the disk.rescue device has a boot order of 1 + disk_path = backend.disks['disk.rescue'].path + query = "devices/disk[source/@file = '%s']/boot/@order" % disk_path + self.assertEqual('1', domain.xpath(query)[0]) + @mock.patch.object(libvirt_utils, 'get_instance_path') @mock.patch.object(libvirt_utils, 'load_file') @mock.patch.object(host.Host, '_get_domain') diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index edaf5a811a59..9539dfe91d2e 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -333,6 +333,7 @@ class LibvirtDriver(driver.ComputeDriver): "supports_image_type_ploop": requires_ploop_image, "supports_pcpus": True, "supports_accelerators": True, + "supports_bfv_rescue": True, } super(LibvirtDriver, self).__init__(virtapi) @@ -3462,7 +3463,14 @@ class LibvirtDriver(driver.ComputeDriver): image_meta = objects.ImageMeta.from_image_ref( context, self._image_api, instance.image_ref) else: - image_meta = objects.ImageMeta.from_dict({}) + # NOTE(lyarwood): If instance.image_ref isn't set attempt to + # lookup the original image_meta from the bdms. This will + # return an empty dict if no valid image_meta is found. + image_meta_dict = utils.get_bdm_image_metadata( + context, self._image_api, self._volume_api, + block_device_info['block_device_mapping'], + legacy_bdm=False) + image_meta = objects.ImageMeta.from_dict(image_meta_dict) else: LOG.info("Attempting an unstable device rescue", instance=instance)