Skip to content

Commit 527c452

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
1 parent bd7e374 commit 527c452

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
@@ -54,6 +56,13 @@ def setUp(self):
5456
_p = mock.patch('nova.virt.libvirt.host.Host.get_connection')
5557
self.mock_conn = _p.start()
5658
self.addCleanup(_p.stop)
59+
# As above, mock the 'get_arch' function as we may need to provide
60+
# different host architectures during some tests.
61+
_a = mock.patch('nova.virt.libvirt.utils.get_arch')
62+
self.mock_arch = _a.start()
63+
# Default to X86_64
64+
self.mock_arch.return_value = obj_fields.Architecture.X86_64
65+
self.addCleanup(_a.stop)
5766

5867
def _setup_compute_service(self):
5968
# 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
@@ -815,6 +815,29 @@ def test_fail_get_disk_bus_for_disk_dev(self):
815815
self.assertRaises(exception.NovaException,
816816
blockinfo.get_disk_bus_for_disk_dev, 'inv', 'val')
817817

818+
@mock.patch('nova.virt.libvirt.utils.get_machine_type')
819+
@mock.patch('nova.virt.libvirt.utils.get_arch')
820+
def test_get_disk_bus_for_device_type_cdrom_with_q35_get_arch(self,
821+
mock_get_arch, mock_get_machine_type):
822+
instance = objects.Instance(**self.test_instance)
823+
mock_get_machine_type.return_value = 'pc-q35-rhel8.0.0'
824+
mock_get_arch.return_value = obj_fields.Architecture.X86_64
825+
image_meta = {'properties': {}}
826+
image_meta = objects.ImageMeta.from_dict(image_meta)
827+
bus = blockinfo.get_disk_bus_for_device_type(instance, 'kvm',
828+
image_meta,
829+
device_type='cdrom')
830+
self.assertEqual('sata', bus)
831+
832+
def test_get_disk_bus_for_device_type_cdrom_with_q35_image_meta(self):
833+
instance = objects.Instance(**self.test_instance)
834+
image_meta = {'properties': {'hw_machine_type': 'pc-q35-rhel8.0.0'}}
835+
image_meta = objects.ImageMeta.from_dict(image_meta)
836+
bus = blockinfo.get_disk_bus_for_device_type(instance, 'kvm',
837+
image_meta,
838+
device_type='cdrom')
839+
self.assertEqual('sata', bus)
840+
818841
def test_get_config_drive_type_default(self):
819842
config_drive_type = blockinfo.get_config_drive_type()
820843
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
@@ -6231,10 +6231,13 @@ def test_get_guest_config_os_command_empty(self):
62316231
image_meta, disk_info)
62326232
self.assertNotEqual(cfg.os_cmdline, "")
62336233

6234+
@mock.patch('nova.virt.libvirt.utils.get_arch',
6235+
return_value=fields.Architecture.ARMV7)
62346236
@mock.patch.object(libvirt_driver.LibvirtDriver,
62356237
"_get_guest_storage_config")
62366238
@mock.patch.object(libvirt_driver.LibvirtDriver, "_has_numa_support")
6237-
def test_get_guest_config_armv7(self, mock_numa, mock_storage):
6239+
def test_get_guest_config_armv7(self, mock_numa, mock_storage,
6240+
mock_get_arch):
62386241
def get_host_capabilities_stub(self):
62396242
cpu = vconfig.LibvirtConfigGuestCPU()
62406243
cpu.arch = fields.Architecture.ARMV7
@@ -6263,12 +6266,14 @@ def get_host_capabilities_stub(self):
62636266
image_meta, disk_info)
62646267
self.assertEqual(cfg.os_mach_type, "virt")
62656268

6269+
@mock.patch('nova.virt.libvirt.utils.get_arch',
6270+
return_value=fields.Architecture.AARCH64)
62666271
@mock.patch.object(libvirt_driver.LibvirtDriver,
62676272
"_get_guest_storage_config")
62686273
@mock.patch.object(libvirt_driver.LibvirtDriver, "_has_numa_support")
62696274
@mock.patch('os.path.exists', return_value=True)
62706275
def test_get_guest_config_aarch64(self, mock_path_exists,
6271-
mock_numa, mock_storage):
6276+
mock_numa, mock_storage, mock_get_arch):
62726277
def get_host_capabilities_stub(self):
62736278
cpu = vconfig.LibvirtConfigGuestCPU()
62746279
cpu.arch = fields.Architecture.AARCH64
@@ -6315,12 +6320,15 @@ def get_host_capabilities_stub(self):
63156320

63166321
self.assertEqual(TEST_AMOUNT_OF_PCIE_SLOTS, num_ports)
63176322

6323+
@mock.patch('nova.virt.libvirt.utils.get_arch',
6324+
return_value=fields.Architecture.AARCH64)
63186325
@mock.patch.object(libvirt_driver.LibvirtDriver,
63196326
"_get_guest_storage_config")
63206327
@mock.patch.object(libvirt_driver.LibvirtDriver, "_has_numa_support")
63216328
@mock.patch('os.path.exists', return_value=True)
63226329
def test_get_guest_config_aarch64_with_graphics(self, mock_path_exists,
6323-
mock_numa, mock_storage):
6330+
mock_numa, mock_storage,
6331+
mock_get_arch):
63246332
def get_host_capabilities_stub(self):
63256333
cpu = vconfig.LibvirtConfigGuestCPU()
63266334
cpu.arch = fields.Architecture.AARCH64
@@ -6356,18 +6364,14 @@ def get_host_capabilities_stub(self):
63566364
self.assertTrue(usbhost_exists)
63576365
self.assertTrue(keyboard_exists)
63586366

6359-
def test_get_guest_config_machine_type_s390(self):
6360-
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
6361-
6362-
caps = vconfig.LibvirtConfigCaps()
6363-
caps.host = vconfig.LibvirtConfigCapsHost()
6364-
caps.host.cpu = vconfig.LibvirtConfigGuestCPU()
6367+
@mock.patch('nova.virt.libvirt.utils.get_arch')
6368+
def test_get_guest_config_machine_type_s390(self, mock_get_arch):
63656369

63666370
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
63676371
host_cpu_archs = (fields.Architecture.S390, fields.Architecture.S390X)
63686372
for host_cpu_arch in host_cpu_archs:
6369-
caps.host.cpu.arch = host_cpu_arch
6370-
os_mach_type = drvr._get_machine_type(image_meta, caps)
6373+
mock_get_arch.return_value = host_cpu_arch
6374+
os_mach_type = libvirt_utils.get_machine_type(image_meta)
63716375
self.assertEqual('s390-ccw-virtio', os_mach_type)
63726376

63736377
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
@@ -4324,36 +4324,6 @@ def _get_guest_config_meta(self, instance):
43244324

43254325
return meta
43264326

4327-
def _get_machine_type(self, image_meta, caps):
4328-
# The guest machine type can be set as an image metadata
4329-
# property, or otherwise based on architecture-specific
4330-
# defaults.
4331-
mach_type = None
4332-
4333-
if image_meta.properties.get('hw_machine_type') is not None:
4334-
mach_type = image_meta.properties.hw_machine_type
4335-
else:
4336-
# NOTE(kchamart): For ARMv7 and AArch64, use the 'virt'
4337-
# board as the default machine type. It is the recommended
4338-
# board, which is designed to be used with virtual machines.
4339-
# The 'virt' board is more flexible, supports PCI, 'virtio',
4340-
# has decent RAM limits, etc.
4341-
if caps.host.cpu.arch in (fields.Architecture.ARMV7,
4342-
fields.Architecture.AARCH64):
4343-
mach_type = "virt"
4344-
4345-
if caps.host.cpu.arch in (fields.Architecture.S390,
4346-
fields.Architecture.S390X):
4347-
mach_type = 's390-ccw-virtio'
4348-
4349-
# If set in the config, use that as the default.
4350-
mach_type = (
4351-
libvirt_utils.get_default_machine_type(caps.host.cpu.arch)
4352-
or mach_type
4353-
)
4354-
4355-
return mach_type
4356-
43574327
@staticmethod
43584328
def _create_idmaps(klass, map_strings):
43594329
idmaps = []
@@ -5041,7 +5011,7 @@ def _configure_guest_by_virt_type(self, guest, virt_type, caps, instance,
50415011
guest.os_loader_type = "pflash"
50425012
else:
50435013
raise exception.UEFINotSupported()
5044-
guest.os_mach_type = self._get_machine_type(image_meta, caps)
5014+
guest.os_mach_type = libvirt_utils.get_machine_type(image_meta)
50455015
if image_meta.properties.get('hw_boot_menu') is None:
50465016
guest.os_bootmenu = strutils.bool_from_string(
50475017
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)