Fix deepcopy usage for BlockDeviceMapping in get_root_info

The method get_root_info sometimes receives a BlockDeviceMapping object,
which lacks a copy method. The previous code assumed root_bdm was always
an instance of DriverBlockDevice, a subclass of dict that supports the copy()
method. However, during testing, it was discovered that root_bdm could also
be a BlockDeviceMapping object, which does not have a copy method.

To address this, the change replaces the copy() call with copy.deepcopy()
according to the suggestion in the comment [1], which works for both
BlockDeviceMapping and DriverBlockDevice instances. The deepcopy method is
supported because oslo.versionedobjects implements the __deepcopy__ method.

This change ensures the function handles both object types correctly,
preventing the AttributeError observed during testing.

[1] https://review.opendev.org/c/openstack/nova/+/909611/4/nova/virt/libvirt/blockinfo.py

Change-Id: I9432718586855ff57e8e6a5cae064e0685dd01e8
(cherry picked from commit 065bf99fc7)
Signed-off-by: Zhang Hua <joshua.zhang@canonical.com>
(cherry picked from commit 9ff4953954)
(cherry picked from commit 608a73ee68)
This commit is contained in:
Zhang Hua
2024-05-24 15:49:12 +08:00
committed by Elod Illes
parent 0fdd21fb4b
commit 5b57acbbda
2 changed files with 49 additions and 3 deletions

View File

@@ -1289,6 +1289,7 @@ class LibvirtBlockInfoTest(test.NoDBTestCase):
@mock.patch('nova.virt.libvirt.blockinfo.get_info_from_bdm')
def test_get_root_info_bdm(self, mock_get_info):
# call get_root_info() with DriverBlockDevice
instance = objects.Instance(**self.test_instance)
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
root_bdm = {'mount_device': '/dev/vda',
@@ -1318,6 +1319,49 @@ class LibvirtBlockInfoTest(test.NoDBTestCase):
{}, 'virtio')
mock_get_info.reset_mock()
@mock.patch('nova.virt.libvirt.blockinfo.get_info_from_bdm')
def test_get_root_info_bdm_with_deepcopy(self, mock_get_info):
# call get_root_info() with BlockDeviceMapping
instance = objects.Instance(**self.test_instance)
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
root_bdm = objects.BlockDeviceMapping(self.context,
**fake_block_device.FakeDbBlockDeviceDict(
{'id': 3, 'instance_uuid': uuids.instance,
'device_name': '/dev/sda',
'source_type': 'blank',
'destination_type': 'local',
'device_type': 'cdrom',
'disk_bus': 'virtio',
'volume_id': 'fake-volume-id-1',
'boot_index': 0}))
# No root_device_name
blockinfo.get_root_info(
instance, 'kvm', image_meta, root_bdm, 'virtio', 'ide')
mock_get_info.assert_called_once_with(
instance, 'kvm', image_meta, root_bdm, {}, 'virtio')
mock_get_info.reset_mock()
# Both device names
blockinfo.get_root_info(
instance, 'kvm', image_meta, root_bdm, 'virtio', 'scsi',
root_device_name='/dev/sda')
mock_get_info.assert_called_once_with(
instance, 'kvm', image_meta, root_bdm, {}, 'virtio')
mock_get_info.reset_mock()
# Missing device names
original_bdm = copy.deepcopy(root_bdm)
root_bdm.device_name = ''
blockinfo.get_root_info(
instance, 'kvm', image_meta, root_bdm, 'virtio', 'scsi',
root_device_name='/dev/sda')
mock_get_info.assert_called_with(
instance, 'kvm', image_meta, mock.ANY, {}, 'virtio')
actual_call = mock_get_info.call_args
_, _, _, actual_bdm, _, _ = actual_call[0]
self.assertEqual(
original_bdm.obj_to_primitive(),
actual_bdm.obj_to_primitive()
)
def test_get_boot_order_simple(self):
disk_info = {
'disk_bus': 'virtio',

View File

@@ -69,6 +69,7 @@ variables / types used
"""
import copy
import itertools
import operator
@@ -444,12 +445,13 @@ def get_root_info(instance, virt_type, image_meta, root_bdm,
'dev': block_device.strip_dev(root_device_name),
'boot_index': '1'}
root_bdm_copy = root_bdm
if not get_device_name(root_bdm) and root_device_name:
root_bdm = root_bdm.copy()
root_bdm['device_name'] = root_device_name
root_bdm_copy = copy.deepcopy(root_bdm)
root_bdm_copy['device_name'] = root_device_name
return get_info_from_bdm(
instance, virt_type, image_meta, root_bdm, {}, disk_bus,
instance, virt_type, image_meta, root_bdm_copy, {}, disk_bus,
)