Merge "libvirt: Add support for virtio-based input devices"

This commit is contained in:
Zuul 2021-03-07 14:24:16 +00:00 committed by Gerrit Code Review
commit 8c951aaf3d
12 changed files with 180 additions and 47 deletions

View File

@ -4,5 +4,5 @@
"hw_architecture": "x86_64"
},
"nova_object.name": "ImageMetaPropsPayload",
"nova_object.version": "1.6"
"nova_object.version": "1.7"
}

View File

@ -296,8 +296,8 @@ Generic property to specify the pointer type.
Input devices allow interaction with a graphical framebuffer. For
example to provide a graphic tablet for absolute cursor movement.
If set, the 'hw_pointer_model' image property takes precedence over
this configuration option.
If set, either the ``hw_input_bus`` or ``hw_pointer_model`` image metadata
properties will take precedence over this configuration option.
Related options:

View File

@ -123,7 +123,8 @@ class ImageMetaPropsPayload(base.NotificationPayloadBase):
# Version 1.4: Added 'mixed' to hw_cpu_policy field
# Version 1.5: Added 'hw_tpm_model' and 'hw_tpm_version' fields
# Version 1.6: Added 'socket' to hw_pci_numa_affinity_policy
VERSION = '1.6'
# Version 1.7: Added 'hw_input_bus' field
VERSION = '1.7'
SCHEMA = {
k: ('image_meta_props', k) for k in image_meta.ImageMetaProps.fields}

View File

@ -465,6 +465,14 @@ class ImageSignatureKeyType(BaseNovaEnum):
)
class InputBus(BaseNovaEnum):
USB = 'usb'
VIRTIO = 'virtio'
ALL = (USB, VIRTIO)
class MigrationType(BaseNovaEnum):
MIGRATION = 'migration' # cold migration
@ -793,7 +801,7 @@ class PointerModelType(BaseNovaEnum):
USBTABLET = "usbtablet"
ALL = (USBTABLET)
ALL = (USBTABLET,)
class NotificationPriority(BaseNovaEnum):
@ -1236,6 +1244,10 @@ class ImageSignatureKeyTypeField(BaseEnumField):
AUTO_TYPE = ImageSignatureKeyType()
class InputBusField(BaseEnumField):
AUTO_TYPE = InputBus()
class MigrationTypeField(BaseEnumField):
AUTO_TYPE = MigrationType()

View File

@ -177,14 +177,17 @@ class ImageMetaProps(base.NovaObject):
# Version 1.26: Added 'mixed' to 'hw_cpu_policy' field
# Version 1.27: Added 'hw_tpm_model' and 'hw_tpm_version' fields
# Version 1.28: Added 'socket' to 'hw_pci_numa_affinity_policy'
# Version 1.29: Added 'hw_input_bus' field
# NOTE(efried): When bumping this version, the version of
# ImageMetaPropsPayload must also be bumped. See its docstring for details.
VERSION = '1.28'
VERSION = '1.29'
def obj_make_compatible(self, primitive, target_version):
super(ImageMetaProps, self).obj_make_compatible(primitive,
target_version)
target_version = versionutils.convert_version_to_tuple(target_version)
if target_version < (1, 29):
primitive.pop('hw_input_bus', None)
if target_version < (1, 28):
policy = primitive.get('hw_pci_numa_affinity_policy', None)
if policy == fields.PCINUMAAffinityPolicy.SOCKET:
@ -330,6 +333,9 @@ class ImageMetaProps(base.NovaObject):
# This indicates the guest needs UEFI firmware
'hw_firmware_type': fields.FirmwareTypeField(),
# name of the input bus type to use, e.g. usb, virtio
'hw_input_bus': fields.InputBusField(),
# boolean - used to trigger code to inject networking when booting a CD
# image with a network boot image
'hw_ipxe_boot': fields.FlexibleBooleanField(),

View File

@ -1233,7 +1233,7 @@ class TestInstanceNotificationSample(
'nova_object.data': {},
'nova_object.name': 'ImageMetaPropsPayload',
'nova_object.namespace': 'nova',
'nova_object.version': u'1.6',
'nova_object.version': '1.7',
},
'image.size': 58145823,
'image.tags': [],
@ -1329,7 +1329,7 @@ class TestInstanceNotificationSample(
'nova_object.data': {},
'nova_object.name': 'ImageMetaPropsPayload',
'nova_object.namespace': 'nova',
'nova_object.version': u'1.6',
'nova_object.version': '1.7',
},
'image.size': 58145823,
'image.tags': [],

View File

@ -387,7 +387,7 @@ notification_object_data = {
# ImageMetaProps, so when you see a fail here for that reason, you must
# *also* bump the version of ImageMetaPropsPayload. See its docstring for
# more information.
'ImageMetaPropsPayload': '1.6-2be4d0bdd1d19a541c46a0d69e244d3f',
'ImageMetaPropsPayload': '1.7-652a6048036c0d0cb8740ea62521c459',
'InstanceActionNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
'InstanceActionPayload': '1.8-4fa3da9cbf0761f1f700ae578f36dc2f',
'InstanceActionRebuildNotification':

View File

@ -349,6 +349,22 @@ class TestImageMetaProps(test.NoDBTestCase):
self.assertRaises(exception.ObjectActionError,
obj.obj_to_primitive, '1.0')
def test_obj_make_compatible_input_bus(self):
"""Check 'hw_input_bus' compatibility."""
# assert that 'hw_input_bus' is supported on a suitably new version
obj = objects.ImageMetaProps(
hw_input_bus=objects.fields.InputBus.VIRTIO,
)
primitive = obj.obj_to_primitive('1.29')
self.assertIn('hw_input_bus', primitive['nova_object.data'])
self.assertEqual(
objects.fields.InputBus.VIRTIO,
primitive['nova_object.data']['hw_input_bus'])
# and is absent on older versions
primitive = obj.obj_to_primitive('1.28')
self.assertNotIn('hw_input_bus', primitive['nova_object.data'])
def test_obj_make_compatible_video_model(self):
# assert that older video models are preserved.
obj = objects.ImageMetaProps(

View File

@ -1074,7 +1074,7 @@ object_data = {
'HyperVLiveMigrateData': '1.4-e265780e6acfa631476c8170e8d6fce0',
'IDEDeviceBus': '1.0-29d4c9f27ac44197f01b6ac1b7e16502',
'ImageMeta': '1.8-642d1b2eb3e880a367f37d72dd76162d',
'ImageMetaProps': '1.28-a55674868f9e319a6b49d688e558d0aa',
'ImageMetaProps': '1.29-5c02bd7b1e050ef39513d3fca728e543',
'Instance': '2.7-d187aec68cad2e4d8b8a03a68e4739ce',
'InstanceAction': '1.2-9a5abc87fdd3af46f45731960651efb5',
'InstanceActionEvent': '1.4-5b1f361bd81989f8bb2c20bb7e8a4cb4',

View File

@ -5796,25 +5796,38 @@ class LibvirtConnTestCase(test.NoDBTestCase,
cfg = self._get_guest_config_with_graphics()
self.assertEqual(len(cfg.devices), 9)
if guestarch != fields.Architecture.AARCH64:
self.assertEqual(len(cfg.devices), 9)
else:
# AArch64 adds a keyboard by default
self.assertEqual(len(cfg.devices), 10)
devices = iter(cfg.devices)
self.assertIsInstance(
cfg.devices[0], vconfig.LibvirtConfigGuestDisk)
next(devices), vconfig.LibvirtConfigGuestDisk)
self.assertIsInstance(
cfg.devices[1], vconfig.LibvirtConfigGuestDisk)
next(devices), vconfig.LibvirtConfigGuestDisk)
self.assertIsInstance(
cfg.devices[2], vconfig.LibvirtConfigGuestSerial)
next(devices), vconfig.LibvirtConfigGuestSerial)
self.assertIsInstance(
cfg.devices[3], vconfig.LibvirtConfigGuestChannel)
next(devices), vconfig.LibvirtConfigGuestChannel)
self.assertIsInstance(
cfg.devices[4], vconfig.LibvirtConfigGuestGraphics)
next(devices), vconfig.LibvirtConfigGuestGraphics)
self.assertIsInstance(
cfg.devices[5], vconfig.LibvirtConfigGuestVideo)
next(devices), vconfig.LibvirtConfigGuestVideo)
if guestarch == fields.Architecture.AARCH64:
# AArch64 adds a keyboard by default
self.assertIsInstance(
next(devices), vconfig.LibvirtConfigGuestInput)
self.assertIsInstance(
cfg.devices[6], vconfig.LibvirtConfigGuestRng)
next(devices), vconfig.LibvirtConfigGuestRng)
self.assertIsInstance(
cfg.devices[7], vconfig.LibvirtConfigGuestUSBHostController)
next(devices), vconfig.LibvirtConfigGuestUSBHostController)
self.assertIsInstance(
cfg.devices[8], vconfig.LibvirtConfigMemoryBalloon)
next(devices), vconfig.LibvirtConfigMemoryBalloon)
self.assertEqual(cfg.devices[3].target_name, "com.redhat.spice.0")
self.assertEqual(cfg.devices[3].type, 'spicevmc')
@ -6400,6 +6413,18 @@ class LibvirtConnTestCase(test.NoDBTestCase,
dev = self._test_guest_add_pointer_device(image_meta)
self.assertIsNotNone(dev)
image_meta = {'properties': {'hw_input_bus': 'usb'}}
dev = self._test_guest_add_pointer_device(image_meta)
self.assertEqual('tablet', dev.type)
self.assertEqual('usb', dev.bus)
image_meta = {'properties': {'hw_input_bus': 'virtio'}}
dev = self._test_guest_add_pointer_device(image_meta)
self.assertEqual('tablet', dev.type)
self.assertEqual('virtio', dev.bus)
@mock.patch.object(libvirt_driver.LOG, 'warning')
def test_guest_add_pointer_device__fail_with_spice_agent(self, mock_warn):
self.flags(enabled=False, group='vnc')
@ -6437,6 +6462,48 @@ class LibvirtConnTestCase(test.NoDBTestCase,
str(mock_warn.call_args[0]),
)
def _test_guest_add_keyboard_device(self, image_meta, arch=None):
self.mock_uname.return_value = fakelibvirt.os_uname(
'Linux', '', '5.4.0-0-generic', '',
arch or fields.Architecture.X86_64)
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
guest = vconfig.LibvirtConfigGuest()
guest.os_type = fields.VMMode.HVM
image_meta = objects.ImageMeta.from_dict({'properties': image_meta})
return drvr._guest_add_keyboard_device(guest, image_meta)
def test_guest_add_keyboard_device(self):
props = {}
dev = self._test_guest_add_keyboard_device(props)
self.assertIsNone(dev)
props = {'hw_input_bus': 'usb'}
dev = self._test_guest_add_keyboard_device(props)
self.assertEqual('usb', dev.bus)
self.assertEqual('keyboard', dev.type)
props = {'hw_input_bus': 'virtio'}
dev = self._test_guest_add_keyboard_device(props)
self.assertEqual('virtio', dev.bus)
self.assertEqual('keyboard', dev.type)
props = {'hw_architecture': fields.Architecture.AARCH64}
dev = self._test_guest_add_keyboard_device(props)
self.assertEqual('usb', dev.bus)
self.assertEqual('keyboard', dev.type)
props = {}
dev = self._test_guest_add_keyboard_device(
props, arch=fields.Architecture.AARCH64)
self.assertEqual('usb', dev.bus)
self.assertEqual('keyboard', dev.type)
def test_get_guest_config_with_watchdog_action_flavor(self):
self.flags(virt_type='kvm', group='libvirt')

View File

@ -6197,17 +6197,6 @@ class LibvirtDriver(driver.ComputeDriver):
return True
return False
def _guest_add_usb_host_keyboard(self, guest):
"""Add USB Host controller and keyboard for graphical console use.
Add USB keyboard as PS/2 support may not be present on non-x86
architectures.
"""
keyboard = vconfig.LibvirtConfigGuestInput()
keyboard.type = "keyboard"
keyboard.bus = "usb"
guest.add_device(keyboard)
def _get_guest_config(self, instance, network_info, image_meta,
disk_info, rescue=None, block_device_info=None,
context=None, mdevs=None, accel_info=None):
@ -6307,14 +6296,7 @@ class LibvirtDriver(driver.ComputeDriver):
self._add_video_driver(guest, image_meta, flavor)
self._guest_add_pointer_device(guest, image_meta)
# We want video == we want graphical console. Some architectures
# do not have input devices attached in default configuration.
# Let then add USB Host controller and USB keyboard.
# x86(-64) and ppc64 have usb host controller and keyboard
# s390x does not support USB
if caps.host.cpu.arch == fields.Architecture.AARCH64:
self._guest_add_usb_host_keyboard(guest)
self._guest_add_keyboard_device(guest, image_meta)
# Some features are only supported 'qemu' and 'kvm' hypervisor
if virt_type in ('qemu', 'kvm'):
@ -6568,11 +6550,25 @@ class LibvirtDriver(driver.ComputeDriver):
return add_video_driver
def _guest_add_pointer_device(self, guest, image_meta):
"""Build the pointer device to add to the instance.
The configuration is determined by examining the 'hw_input_bus' image
metadata property, the 'hw_pointer_model' image metadata property, and
the '[DEFAULT] pointer_model' config option in that order.
"""
pointer_bus = image_meta.properties.get('hw_input_bus')
pointer_model = image_meta.properties.get('hw_pointer_model')
# If the user hasn't requested anything and the host config says to use
# something other than a USB tablet, there's nothing to do
if pointer_model is None and CONF.pointer_model in (None, 'ps2mouse'):
if pointer_bus:
pointer_model = 'tablet'
pointer_bus = pointer_bus
elif pointer_model or CONF.pointer_model == 'usbtablet':
# Handle the legacy 'hw_pointer_model' image metadata property
pointer_model = 'tablet'
pointer_bus = 'usb'
else:
# If the user hasn't requested anything and the host config says to
# use something other than a USB tablet, there's nothing to do
return
# For backward compatibility, we don't want to error out if the host
@ -6599,12 +6595,37 @@ class LibvirtDriver(driver.ComputeDriver):
'configuration.')
return
tablet = vconfig.LibvirtConfigGuestInput()
tablet.type = 'tablet'
tablet.bus = 'usb'
guest.add_device(tablet)
pointer = vconfig.LibvirtConfigGuestInput()
pointer.type = pointer_model
pointer.bus = pointer_bus
guest.add_device(pointer)
# returned for unit testing purposes
return tablet
return pointer
def _guest_add_keyboard_device(self, guest, image_meta):
"""Add keyboard for graphical console use."""
bus = image_meta.properties.get('hw_input_bus')
if not bus:
# AArch64 doesn't provide a default keyboard so we explicitly add
# one; for everything else we rely on default (e.g. for x86,
# libvirt will automatically add a PS2 keyboard)
# TODO(stephenfin): We might want to do this for other non-x86
# architectures
arch = libvirt_utils.get_arch(image_meta)
if arch != fields.Architecture.AARCH64:
return None
bus = 'usb'
keyboard = vconfig.LibvirtConfigGuestInput()
keyboard.type = 'keyboard'
keyboard.bus = bus
guest.add_device(keyboard)
# returned for unit testing purposes
return keyboard
def _get_guest_xml(self, context, instance, network_info, disk_info,
image_meta, rescue=None,

View File

@ -0,0 +1,10 @@
---
features:
- |
A new image metadata property, ``hw_input_bus``, has been added. This
allows you to specify the bus used for input devices - a pointer and
keyboard - which are attached to the instance when graphics are enabled
on compute nodes using the libvirt virt driver. Two values are currently
accepted: ``usb`` and ``virtio``. This image metadata property effectively
replaced the ``hw_pointer_model`` image metadata property, which is
nontheless retained for backwards compatibility purposes.