Skip to content

Commit db40cc4

Browse files
committed
libvirt: Use SATA bus for cdrom devices when using Q35 machine type
The Q35 machine type no longer provides an IDE bus and will need to use a SATA bus to attach legacy devices such as cdroms. More details can be found in the following related bug: Don't assume the guest machine type to be of 'pc' https://bugs.launchpad.net/nova/+bug/1780138 This change now ensures the blockinfo.get_disk_bus_for_device_type method will now return "sata" as the bus type when the Q35 machine type is used for cdrom devices on QEMU or KVM hosts that are not PPC, S390 or AArch64 based. To enable this the _get_machine_type method has been extracted from the Libvirt driver into the Libvirt utils module. This method has also been simplified through the removal of the caps parameter, replaced with calls to the get_arch utility method and additional extraction of architecture specific defaults into the existing get_default_machine_type utility method. Related-bug: 1780138 Closes-bug: 1831538 Change-Id: Id97f4baddcf2caff91599773d9b5de5181b7fdf6 (cherry picked from commit 527c452)
1 parent 0cb6106 commit db40cc4

File tree

7 files changed

+98
-51
lines changed

7 files changed

+98
-51
lines changed

nova/tests/functional/libvirt/base.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import fixtures
1818
import mock
1919

20+
from nova.objects import fields as obj_fields
21+
2022
from nova.tests import fixtures as nova_fixtures
2123
from nova.tests.functional import fixtures as func_fixtures
2224
from nova.tests.functional import test_servers as base
@@ -52,6 +54,13 @@ def setUp(self):
5254
_p = mock.patch('nova.virt.libvirt.host.Host.get_connection')
5355
self.mock_conn = _p.start()
5456
self.addCleanup(_p.stop)
57+
# As above, mock the 'get_arch' function as we may need to provide
58+
# different host architectures during some tests.
59+
_a = mock.patch('nova.virt.libvirt.utils.get_arch')
60+
self.mock_arch = _a.start()
61+
# Default to X86_64
62+
self.mock_arch.return_value = obj_fields.Architecture.X86_64
63+
self.addCleanup(_a.stop)
5564

5665
def _setup_compute_service(self):
5766
# NOTE(stephenfin): We don't start the compute service here as we wish

nova/tests/unit/virt/libvirt/fake_libvirt_utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,10 @@ def cpu_features_to_traits(features):
179179
return libvirt_utils.cpu_features_to_traits(features)
180180

181181

182+
def get_machine_type(image_meta):
183+
return libvirt_utils.get_machine_type(image_meta)
184+
185+
182186
def get_default_machine_type(arch):
183187
return libvirt_utils.get_default_machine_type(arch)
184188

nova/tests/unit/virt/libvirt/test_blockinfo.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,29 @@ def test_fail_get_disk_bus_for_disk_dev(self):
814814
self.assertRaises(exception.NovaException,
815815
blockinfo.get_disk_bus_for_disk_dev, 'inv', 'val')
816816

817+
@mock.patch('nova.virt.libvirt.utils.get_machine_type')
818+
@mock.patch('nova.virt.libvirt.utils.get_arch')
819+
def test_get_disk_bus_for_device_type_cdrom_with_q35_get_arch(self,
820+
mock_get_arch, mock_get_machine_type):
821+
instance = objects.Instance(**self.test_instance)
822+
mock_get_machine_type.return_value = 'pc-q35-rhel8.0.0'
823+
mock_get_arch.return_value = obj_fields.Architecture.X86_64
824+
image_meta = {'properties': {}}
825+
image_meta = objects.ImageMeta.from_dict(image_meta)
826+
bus = blockinfo.get_disk_bus_for_device_type(instance, 'kvm',
827+
image_meta,
828+
device_type='cdrom')
829+
self.assertEqual('sata', bus)
830+
831+
def test_get_disk_bus_for_device_type_cdrom_with_q35_image_meta(self):
832+
instance = objects.Instance(**self.test_instance)
833+
image_meta = {'properties': {'hw_machine_type': 'pc-q35-rhel8.0.0'}}
834+
image_meta = objects.ImageMeta.from_dict(image_meta)
835+
bus = blockinfo.get_disk_bus_for_device_type(instance, 'kvm',
836+
image_meta,
837+
device_type='cdrom')
838+
self.assertEqual('sata', bus)
839+
817840
def test_get_config_drive_type_default(self):
818841
config_drive_type = blockinfo.get_config_drive_type()
819842
self.assertEqual('cdrom', config_drive_type)

nova/tests/unit/virt/libvirt/test_driver.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6195,10 +6195,13 @@ def test_get_guest_config_os_command_empty(self):
61956195
image_meta, disk_info)
61966196
self.assertNotEqual(cfg.os_cmdline, "")
61976197

6198+
@mock.patch('nova.virt.libvirt.utils.get_arch',
6199+
return_value=fields.Architecture.ARMV7)
61986200
@mock.patch.object(libvirt_driver.LibvirtDriver,
61996201
"_get_guest_storage_config")
62006202
@mock.patch.object(libvirt_driver.LibvirtDriver, "_has_numa_support")
6201-
def test_get_guest_config_armv7(self, mock_numa, mock_storage):
6203+
def test_get_guest_config_armv7(self, mock_numa, mock_storage,
6204+
mock_get_arch):
62026205
def get_host_capabilities_stub(self):
62036206
cpu = vconfig.LibvirtConfigGuestCPU()
62046207
cpu.arch = fields.Architecture.ARMV7
@@ -6227,12 +6230,14 @@ def get_host_capabilities_stub(self):
62276230
image_meta, disk_info)
62286231
self.assertEqual(cfg.os_mach_type, "virt")
62296232

6233+
@mock.patch('nova.virt.libvirt.utils.get_arch',
6234+
return_value=fields.Architecture.AARCH64)
62306235
@mock.patch.object(libvirt_driver.LibvirtDriver,
62316236
"_get_guest_storage_config")
62326237
@mock.patch.object(libvirt_driver.LibvirtDriver, "_has_numa_support")
62336238
@mock.patch('os.path.exists', return_value=True)
62346239
def test_get_guest_config_aarch64(self, mock_path_exists,
6235-
mock_numa, mock_storage):
6240+
mock_numa, mock_storage, mock_get_arch):
62366241
def get_host_capabilities_stub(self):
62376242
cpu = vconfig.LibvirtConfigGuestCPU()
62386243
cpu.arch = fields.Architecture.AARCH64
@@ -6279,12 +6284,15 @@ def get_host_capabilities_stub(self):
62796284

62806285
self.assertEqual(TEST_AMOUNT_OF_PCIE_SLOTS, num_ports)
62816286

6287+
@mock.patch('nova.virt.libvirt.utils.get_arch',
6288+
return_value=fields.Architecture.AARCH64)
62826289
@mock.patch.object(libvirt_driver.LibvirtDriver,
62836290
"_get_guest_storage_config")
62846291
@mock.patch.object(libvirt_driver.LibvirtDriver, "_has_numa_support")
62856292
@mock.patch('os.path.exists', return_value=True)
62866293
def test_get_guest_config_aarch64_with_graphics(self, mock_path_exists,
6287-
mock_numa, mock_storage):
6294+
mock_numa, mock_storage,
6295+
mock_get_arch):
62886296
def get_host_capabilities_stub(self):
62896297
cpu = vconfig.LibvirtConfigGuestCPU()
62906298
cpu.arch = fields.Architecture.AARCH64
@@ -6320,18 +6328,14 @@ def get_host_capabilities_stub(self):
63206328
self.assertTrue(usbhost_exists)
63216329
self.assertTrue(keyboard_exists)
63226330

6323-
def test_get_guest_config_machine_type_s390(self):
6324-
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
6325-
6326-
caps = vconfig.LibvirtConfigCaps()
6327-
caps.host = vconfig.LibvirtConfigCapsHost()
6328-
caps.host.cpu = vconfig.LibvirtConfigGuestCPU()
6331+
@mock.patch('nova.virt.libvirt.utils.get_arch')
6332+
def test_get_guest_config_machine_type_s390(self, mock_get_arch):
63296333

63306334
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
63316335
host_cpu_archs = (fields.Architecture.S390, fields.Architecture.S390X)
63326336
for host_cpu_arch in host_cpu_archs:
6333-
caps.host.cpu.arch = host_cpu_arch
6334-
os_mach_type = drvr._get_machine_type(image_meta, caps)
6337+
mock_get_arch.return_value = host_cpu_arch
6338+
os_mach_type = libvirt_utils.get_machine_type(image_meta)
63356339
self.assertEqual('s390-ccw-virtio', os_mach_type)
63366340

63376341
def test_get_guest_config_machine_type_through_image_meta(self):

nova/virt/libvirt/blockinfo.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -222,14 +222,14 @@ def get_disk_bus_for_device_type(instance,
222222
device_type="disk"):
223223
"""Determine the best disk bus to use for a device type.
224224
225-
Considering the currently configured virtualization
226-
type, return the optimal disk_bus to use for a given
227-
device type. For example, for a disk on KVM it will
228-
return 'virtio', while for a CDROM it will return 'ide'
229-
on x86_64 and 'scsi' on ppc64.
230-
231-
Returns the disk_bus, or returns None if the device
232-
type is not supported for this virtualization
225+
Considering the currently configured virtualization type, return the
226+
optimal disk_bus to use for a given device type. For example, for a disk
227+
on KVM it will return 'virtio', while for a CDROM, it will return 'ide'
228+
for the 'pc' machine type on x86_64, 'sata' for the 'q35' machine type on
229+
x86_64 and 'scsi' on ppc64.
230+
231+
Returns the disk_bus, or returns None if the device type is not supported
232+
for this virtualization
233233
"""
234234

235235
# Prefer a disk bus set against the image first of all
@@ -268,6 +268,14 @@ def get_disk_bus_for_device_type(instance,
268268
obj_fields.Architecture.S390X,
269269
obj_fields.Architecture.AARCH64):
270270
return "scsi"
271+
machine_type = libvirt_utils.get_machine_type(image_meta)
272+
# NOTE(lyarwood): We can't be any more explicit here as QEMU
273+
# provides a version of the Q35 machine type per release.
274+
# Additionally downstream distributions can also provide their own.
275+
if machine_type and 'q35' in machine_type:
276+
# NOTE(lyarwood): The Q35 machine type does not provide an IDE
277+
# bus and as such we must use a SATA bus for cdroms.
278+
return "sata"
271279
else:
272280
return "ide"
273281
elif device_type == "disk":

nova/virt/libvirt/driver.py

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4249,36 +4249,6 @@ def _get_guest_config_meta(self, instance):
42494249

42504250
return meta
42514251

4252-
def _get_machine_type(self, image_meta, caps):
4253-
# The guest machine type can be set as an image metadata
4254-
# property, or otherwise based on architecture-specific
4255-
# defaults.
4256-
mach_type = None
4257-
4258-
if image_meta.properties.get('hw_machine_type') is not None:
4259-
mach_type = image_meta.properties.hw_machine_type
4260-
else:
4261-
# NOTE(kchamart): For ARMv7 and AArch64, use the 'virt'
4262-
# board as the default machine type. It is the recommended
4263-
# board, which is designed to be used with virtual machines.
4264-
# The 'virt' board is more flexible, supports PCI, 'virtio',
4265-
# has decent RAM limits, etc.
4266-
if caps.host.cpu.arch in (fields.Architecture.ARMV7,
4267-
fields.Architecture.AARCH64):
4268-
mach_type = "virt"
4269-
4270-
if caps.host.cpu.arch in (fields.Architecture.S390,
4271-
fields.Architecture.S390X):
4272-
mach_type = 's390-ccw-virtio'
4273-
4274-
# If set in the config, use that as the default.
4275-
mach_type = (
4276-
libvirt_utils.get_default_machine_type(caps.host.cpu.arch)
4277-
or mach_type
4278-
)
4279-
4280-
return mach_type
4281-
42824252
@staticmethod
42834253
def _create_idmaps(klass, map_strings):
42844254
idmaps = []
@@ -4952,7 +4922,7 @@ def _configure_guest_by_virt_type(self, guest, virt_type, caps, instance,
49524922
guest.os_loader_type = "pflash"
49534923
else:
49544924
raise exception.UEFINotSupported()
4955-
guest.os_mach_type = self._get_machine_type(image_meta, caps)
4925+
guest.os_mach_type = libvirt_utils.get_machine_type(image_meta)
49564926
if image_meta.properties.get('hw_boot_menu') is None:
49574927
guest.os_bootmenu = strutils.bool_from_string(
49584928
flavor.extra_specs.get('hw:boot_menu', 'no'))

nova/virt/libvirt/utils.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,19 @@ def get_cpu_model_from_arch(arch):
541541
return mode
542542

543543

544+
def get_machine_type(image_meta):
545+
"""The guest machine type can be set as an image metadata property, or
546+
otherwise based on architecture-specific defaults. If no defaults are
547+
found then None will be returned. This will ultimately lead to QEMU using
548+
its own default which is currently the 'pc' machine type.
549+
"""
550+
if image_meta.properties.get('hw_machine_type') is not None:
551+
return image_meta.properties.hw_machine_type
552+
553+
# If set in the config, use that as the default.
554+
return get_default_machine_type(get_arch(image_meta))
555+
556+
544557
def machine_type_mappings():
545558
mappings = {}
546559
for mapping in CONF.libvirt.hw_machine_type or {}:
@@ -553,7 +566,23 @@ def machine_type_mappings():
553566

554567

555568
def get_default_machine_type(arch):
556-
return machine_type_mappings().get(arch)
569+
# NOTE(lyarwood): Values defined in [libvirt]/hw_machine_type take
570+
# precedence here if available for the provided arch.
571+
machine_type = machine_type_mappings().get(arch)
572+
if machine_type:
573+
return machine_type
574+
# NOTE(kchamart): For ARMv7 and AArch64, use the 'virt' board as the
575+
# default machine type. It is the recommended board, which is designed
576+
# to be used with virtual machines. The 'virt' board is more flexible,
577+
# supports PCI, 'virtio', has decent RAM limits, etc.
578+
if arch in (obj_fields.Architecture.ARMV7,
579+
obj_fields.Architecture.AARCH64):
580+
return "virt"
581+
582+
if arch in (obj_fields.Architecture.S390,
583+
obj_fields.Architecture.S390X):
584+
return "s390-ccw-virtio"
585+
return None
557586

558587

559588
def mdev_name2uuid(mdev_name):

0 commit comments

Comments
 (0)