Merge "Implement USB controller extra spec for libvirt."
This commit is contained in:
@@ -552,6 +552,33 @@ feature_flag_validators = [
|
||||
'enum': fields.SoundModelType.ALL,
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='hw:usb_model',
|
||||
description=(
|
||||
'The model of the attached USB controller device. '
|
||||
'Only supported by the libvirt virt driver. '
|
||||
'If unset, no USB controller device is attached.'
|
||||
),
|
||||
value={
|
||||
'type': str,
|
||||
'description': 'A USB controller model',
|
||||
'enum': fields.USBControllerModelType.ALL,
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='hw:redirected_usb_ports',
|
||||
description=(
|
||||
'The number of redirected USB ports to add to the virtual '
|
||||
'machine. Only supported by the libvirt virt driver. If unset, '
|
||||
'no redirected USB ports are added. The maximum value is 15.'
|
||||
),
|
||||
value={
|
||||
'type': int,
|
||||
'description': 'The number of USB redirection devices to add',
|
||||
'min': 0,
|
||||
'max': 15
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
ephemeral_encryption_validators = [
|
||||
|
||||
@@ -125,6 +125,7 @@ MIN_COMPUTE_VDPA_HOTPLUG_LIVE_MIGRATION = 63
|
||||
SUPPORT_SHARES = 67
|
||||
|
||||
MIN_COMPUTE_SOUND_MODEL_TRAITS = 69
|
||||
MIN_COMPUTE_USB_MODEL_TRAITS = 70
|
||||
|
||||
# FIXME(danms): Keep a global cache of the cells we find the
|
||||
# first time we look. This needs to be refreshed on a timer or
|
||||
|
||||
@@ -31,7 +31,7 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# NOTE(danms): This is the global service version counter
|
||||
SERVICE_VERSION = 69
|
||||
SERVICE_VERSION = 70
|
||||
|
||||
|
||||
# NOTE(danms): This is our SERVICE_VERSION history. The idea is that any
|
||||
@@ -240,6 +240,9 @@ SERVICE_VERSION_HISTORY = (
|
||||
# Version 69: Compute RPC v6.4:
|
||||
# Compute manager supports sound model traits
|
||||
{'compute_rpc': '6.4'},
|
||||
# Version 70: Compute RPC v6.4:
|
||||
# Compute manager supports USB controller model traits
|
||||
{'compute_rpc': '6.4'},
|
||||
)
|
||||
|
||||
# This is the version after which we can rely on having a persistent
|
||||
|
||||
@@ -22,6 +22,7 @@ from nova import exception
|
||||
from nova import objects
|
||||
from nova.objects import base as obj_base
|
||||
from nova import test
|
||||
from nova.virt.hardware import get_redirected_usb_ports
|
||||
|
||||
|
||||
class TestValidateExtraSpecKeys(test.NoDBTestCase):
|
||||
@@ -292,3 +293,73 @@ class TestCreateFlavor(test.TestCase):
|
||||
self.assertRaises(
|
||||
exception.FlavorIdExists,
|
||||
flavors.create, 'flavor2', 64, 1, 120, flavorid='flavorid')
|
||||
|
||||
|
||||
class RedirectedUSBPortsFlavorTests(test.TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.image_meta = objects.ImageMeta.from_dict(
|
||||
{
|
||||
'status': 'active',
|
||||
'container_format': 'bare',
|
||||
'min_ram': 0,
|
||||
'updated_at': '2014-12-12T11:16:36.000000',
|
||||
'min_disk': 0,
|
||||
'owner': '2d8b9502858c406ebee60f0849486222',
|
||||
'protected': 'yes',
|
||||
'properties': {
|
||||
'os_type': 'Linux',
|
||||
'hw_video_model': 'vga',
|
||||
'hw_video_ram': '512',
|
||||
'hw_qemu_guest_agent': 'yes',
|
||||
'hw_scsi_model': 'virtio-scsi',
|
||||
},
|
||||
'size': 213581824,
|
||||
'name': 'f16-x86_64-openstack-sda',
|
||||
'checksum': '755122332caeb9f661d5c978adb8b45f',
|
||||
'created_at': '2014-12-10T16:23:14.000000',
|
||||
'disk_format': 'qcow2',
|
||||
'id': 'c8b1790e-a07d-4971-b137-44f2432936cd',
|
||||
}
|
||||
)
|
||||
|
||||
def test_redirected_usb_ports_flavor_specs_integer(self):
|
||||
self.assertEqual(
|
||||
1, get_redirected_usb_ports(
|
||||
{
|
||||
'extra_specs': {
|
||||
'hw:redirected_usb_ports': 1
|
||||
}
|
||||
},
|
||||
self.image_meta))
|
||||
|
||||
def test_redirected_usb_ports_flavor_specs_integer_as_string(self):
|
||||
self.assertEqual(
|
||||
1, get_redirected_usb_ports(
|
||||
{
|
||||
'extra_specs': {
|
||||
'hw:redirected_usb_ports': '1'
|
||||
}
|
||||
},
|
||||
self.image_meta))
|
||||
|
||||
def test_redirected_usb_ports_flavor_specs_integer_is_negative(self):
|
||||
self.assertRaises(exception.Invalid,
|
||||
get_redirected_usb_ports,
|
||||
{
|
||||
'extra_specs': {
|
||||
'hw:redirected_usb_ports': -1
|
||||
}
|
||||
},
|
||||
self.image_meta)
|
||||
|
||||
def test_redirected_usb_ports_flavor_specs_non_integer(self):
|
||||
self.assertRaises(exception.Invalid,
|
||||
get_redirected_usb_ports,
|
||||
{
|
||||
'extra_specs': {
|
||||
'hw:redirected_usb_ports': 'banana'
|
||||
}
|
||||
},
|
||||
self.image_meta)
|
||||
|
||||
@@ -569,6 +569,21 @@ class TestImageMetaProps(test.NoDBTestCase):
|
||||
primitive = obj.obj_to_primitive('1.27')
|
||||
self.assertNotIn('hw_sound_model', primitive['nova_object.data'])
|
||||
|
||||
def test_obj_make_compatible_usb_model_and_redirected_ports(self):
|
||||
"""Check if we pop hw_usb_model and hw_redirected_usb_ports."""
|
||||
obj = objects.ImageMetaProps(
|
||||
hw_usb_model='qemu-xhci',
|
||||
hw_redirected_usb_ports=3
|
||||
)
|
||||
primitive = obj.obj_to_primitive()
|
||||
self.assertIn('hw_usb_model', primitive['nova_object.data'])
|
||||
self.assertIn('hw_redirected_usb_ports', primitive['nova_object.data'])
|
||||
|
||||
primitive = obj.obj_to_primitive('1.27')
|
||||
self.assertNotIn('hw_usb_model', primitive['nova_object.data'])
|
||||
self.assertNotIn(
|
||||
'hw_redirected_usb_ports', primitive['nova_object.data'])
|
||||
|
||||
def test_obj_make_compatible_socket_policy(self):
|
||||
obj = objects.ImageMetaProps(
|
||||
hw_pci_numa_affinity_policy=fields.PCINUMAAffinityPolicy.SOCKET)
|
||||
|
||||
@@ -2940,3 +2940,66 @@ def get_sound_model(
|
||||
)
|
||||
|
||||
return model
|
||||
|
||||
|
||||
def get_usb_model(
|
||||
flavor: 'objects.Flavor',
|
||||
image_meta: 'objects.ImageMeta',
|
||||
) -> ty.Optional[str]:
|
||||
"""Get the USB controller model, if any.
|
||||
|
||||
:param flavor: ``nova.objects.Flavor`` instance
|
||||
:param image_meta: ``nova.objects.ImageMeta`` instance
|
||||
:raises: nova.exception.FlavorImageConflict if a value is specified in both
|
||||
the flavor and the image, but the values do not match
|
||||
:raises: nova.exception.Invalid if a value or combination of values is
|
||||
invalid
|
||||
:returns: A string containing the USB controller model, else None.
|
||||
"""
|
||||
model = _get_unique_flavor_image_meta('usb_model', flavor, image_meta)
|
||||
if model and model not in fields.USBControllerModelType.ALL:
|
||||
raise exception.Invalid(
|
||||
'Invalid USB controller model %(model)r. '
|
||||
'Allowed values: %(valid)s.'
|
||||
% {
|
||||
'model': model,
|
||||
'valid': ', '.join(fields.USBControllerModelType.ALL)
|
||||
})
|
||||
|
||||
return model
|
||||
|
||||
|
||||
def get_redirected_usb_ports(
|
||||
flavor: 'objects.Flavor',
|
||||
image_meta: 'objects.ImageMeta',
|
||||
) -> int:
|
||||
"""Get the number of redirected USB ports, if any.
|
||||
|
||||
:param flavor: ``nova.objects.Flavor`` instance
|
||||
:param image_meta: ``nova.objects.ImageMeta`` instance
|
||||
:raises: nova.exception.FlavorImageConflict if a value is specified in both
|
||||
the flavor and the image, but the values do not match
|
||||
:raises: nova.exception.Invalid if a value or combination of values is
|
||||
invalid
|
||||
:returns: An integer number of ports, else 0.
|
||||
"""
|
||||
count = _get_unique_flavor_image_meta(
|
||||
'redirected_usb_ports', flavor, image_meta)
|
||||
if not count:
|
||||
count = 0
|
||||
else:
|
||||
try:
|
||||
count = int(count)
|
||||
except ValueError:
|
||||
raise exception.Invalid('"%s" is not a valid integer.' % count)
|
||||
|
||||
if count < 0:
|
||||
raise exception.Invalid(
|
||||
'You cannot have a negative number of USB ports.')
|
||||
elif count > 15:
|
||||
# NOTE(mikal): XHCI controllers only support up to 15 ports. This isn't
|
||||
# documented in the libvirt domain XML documentation at the moment, but
|
||||
# is at https://www.kraxel.org/blog/2018/08/qemu-usb-tips/.
|
||||
raise exception.Invalid('Nova only supports up to 15 USB ports.')
|
||||
|
||||
return count
|
||||
|
||||
@@ -4048,3 +4048,19 @@ class LibvirtConfigGuestSound(LibvirtConfigObject):
|
||||
meta = self._new_node('sound')
|
||||
meta.set('model', str(self.model))
|
||||
return meta
|
||||
|
||||
|
||||
class LibvirtConfigGuestUSBRedirect(LibvirtConfigObject):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(LibvirtConfigGuestUSBRedirect,
|
||||
self).__init__(root_name='redirdev')
|
||||
|
||||
self.type = 'spicevmc'
|
||||
self.bus = 'usb'
|
||||
|
||||
def format_dom(self):
|
||||
meta = self._new_node('redirdev')
|
||||
meta.set('type', str(self.type))
|
||||
meta.set('bus', str(self.bus))
|
||||
return meta
|
||||
|
||||
@@ -6901,6 +6901,18 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
sound_device = vconfig.LibvirtConfigGuestSound(sound_model)
|
||||
guest.add_device(sound_device)
|
||||
|
||||
def _add_redirected_usb_ports(
|
||||
self,
|
||||
guest: vconfig.LibvirtConfigGuest,
|
||||
flavor: 'objects.Flavor',
|
||||
instance: 'objects.Instance',
|
||||
image_meta: 'objects.ImageMeta',
|
||||
) -> None:
|
||||
"""Add redirected USB ports, if requested."""
|
||||
count = hardware.get_redirected_usb_ports(flavor, image_meta)
|
||||
for i in range(count):
|
||||
guest.add_device(vconfig.LibvirtConfigGuestUSBRedirect())
|
||||
|
||||
def _set_qemu_guest_agent(self, guest, flavor, instance, image_meta):
|
||||
# Enable qga only if the 'hw_qemu_guest_agent' is equal to yes
|
||||
if image_meta.properties.get('hw_qemu_guest_agent', False):
|
||||
@@ -7414,7 +7426,7 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
|
||||
return False
|
||||
|
||||
def _guest_add_usb_root_controller(self, guest, image_meta):
|
||||
def _guest_add_usb_root_controller(self, guest, flavor, image_meta):
|
||||
"""Add USB root controller, if necessary.
|
||||
|
||||
Note that these are added by default on x86-64. We add the controller
|
||||
@@ -7423,9 +7435,11 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
"""
|
||||
usbhost = vconfig.LibvirtConfigGuestUSBHostController()
|
||||
usbhost.index = 0
|
||||
|
||||
# an unset model means autodetect, while 'none' means don't add a
|
||||
# controller (x86 gets one by default)
|
||||
usbhost.model = None
|
||||
specified_model = hardware.get_usb_model(flavor, image_meta)
|
||||
if not self._guest_needs_usb(guest, image_meta):
|
||||
archs = (
|
||||
fields.Architecture.PPC,
|
||||
@@ -7438,6 +7452,8 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
# xml, where 'none' adds it but then disables it causing
|
||||
# libvirt errors and the instances not being able to build
|
||||
usbhost.model = None
|
||||
elif specified_model:
|
||||
usbhost.model = specified_model
|
||||
else:
|
||||
usbhost.model = 'none'
|
||||
guest.add_device(usbhost)
|
||||
@@ -7602,7 +7618,8 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
if self._guest_needs_pcie(guest):
|
||||
self._guest_add_pcie_root_ports(guest)
|
||||
|
||||
self._guest_add_usb_root_controller(guest, image_meta)
|
||||
self._guest_add_usb_root_controller(guest, flavor, image_meta)
|
||||
self._add_redirected_usb_ports(guest, flavor, instance, image_meta)
|
||||
|
||||
self._guest_add_pci_devices(guest, instance)
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
The `hw:sound_model` flavor extra spec and the matching `hw_sound_model`
|
||||
image property were added to allow the configuration of a sound device
|
||||
within an instance. This is useful with the new spice-direct console
|
||||
type. The default remains no sound device, but when using the libvirt
|
||||
hypervisor driver you can select from `sb16`, `es1370`, `pcspk`, `ac97`,
|
||||
`ich6`, `ich9`, `usb`, and `virtio`. For most use-cases `usb` is
|
||||
likely to be the best choice unless you have at least libvirt 8.2.0 and
|
||||
libvirt 10.4.0.
|
||||
The ``hw:sound_model`` flavor extra spec and the matching
|
||||
``hw_sound_model`` image property were added to allow the
|
||||
configuration of a sound device within an instance. This is useful
|
||||
with the new spice-direct console type. The default remains no sound
|
||||
device, but when using the libvirt hypervisor driver you can select
|
||||
from ``sb16``, ``es1370``, ``pcspk``, ``ac97``, ``ich6``, ``ich9``,
|
||||
``usb``, and ``virtio``. For most use-cases ``usb`` is likely to be
|
||||
the best choice unless you have at least libvirt 8.2.0 and libvirt
|
||||
10.4.0.
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
The ``hw:usb_model`` flavor extra spec and the matching ``hw_usb_model``
|
||||
image property were added to allow the configuration of a USB controller
|
||||
within an instance. This is useful with the new spice-direct console
|
||||
type which supports passing through USB devices from the client to the
|
||||
instance, such as a smart card reader. There is also an additional
|
||||
``hw:redirected_usb_ports`` / ``hw_redirected_usb_ports`` pair which
|
||||
controls how many ports the USB controller has. This number will
|
||||
vary based on the USB controller selected. The default remains no
|
||||
USB controller, but when using the libvirt hypervisor driver you can
|
||||
now also select from ``qemu_xhci`` and ``nec_xhci``.
|
||||
Reference in New Issue
Block a user