Add 'hw:vif_multiqueue_enabled' flavor extra spec
This mirrors the 'hw_vif_multiqueue_enabled' image metadata property. Providing a way to set this via flavor extra specs allows admins to enable this by default and easily enable it for existing instances without the need to rebuild (a destructive operation). Note that, in theory at least, the image import workflow provided by glance should allows admins to enable this by default, but the legacy image create workflow does not allow this and admins cannot really control which API end users use when uploading their own images. Also note that we could provide this behavior using a host-level configuration option. This would be similar to what we do for other attributes such as machine type ('hw_machine_type' image meta prop or '[libvirt] hw_machine_type' config option) or pointer model ('hw_pointer_model' image meta prop or '[compute] pointer_model' config option) and would be well suited to things that we don't expect to change, such as enabling multiqueue (it's a sensible default). However, we would need to start storing this information in system_metadata, like we do for machine type (since Wallaby) to prevent things changing over live migration. We have also started avoiding host-level config options for things like this since one must ensure that the value configured are consistent across deployments to behavior that varies depending on the host the guest is initially created on. Change-Id: I405d0324abe32b31a434105cf2c104876fe9c127 Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
parent
e28afc5647
commit
f42fb1241b
@ -368,6 +368,20 @@ feature_flag_validators = [
|
||||
'description': 'Whether to enable the boot menu',
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='hw:vif_multiqueue_enabled',
|
||||
description=(
|
||||
'Whether to enable the virtio-net multiqueue feature. '
|
||||
'When set, the driver sets the number of queues equal to the '
|
||||
'number of guest vCPUs. This makes the network performance scale '
|
||||
'across a number of vCPUs. This requires guest support and is '
|
||||
'only supported by the libvirt driver.'
|
||||
),
|
||||
value={
|
||||
'type': bool,
|
||||
'description': 'Whether to enable multiqueue',
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='hw:mem_encryption',
|
||||
description=(
|
||||
|
@ -852,6 +852,7 @@ class API:
|
||||
hardware.get_number_of_serial_ports(flavor, image_meta)
|
||||
hardware.get_realtime_cpu_constraint(flavor, image_meta)
|
||||
hardware.get_cpu_topology_constraints(flavor, image_meta)
|
||||
hardware.get_vif_multiqueue_constraint(flavor, image_meta)
|
||||
if validate_numa:
|
||||
hardware.numa_get_constraints(flavor, image_meta)
|
||||
if validate_pci:
|
||||
|
@ -702,17 +702,20 @@ class LibvirtVifTestCase(test.NoDBTestCase):
|
||||
image_meta = objects.ImageMeta.from_dict(
|
||||
{'properties': {'hw_vif_model': 'virtio',
|
||||
'hw_vif_multiqueue_enabled': 'true'}})
|
||||
flavor = objects.Flavor(name='m1.small',
|
||||
memory_mb=128,
|
||||
vcpus=4,
|
||||
root_gb=0,
|
||||
ephemeral_gb=0,
|
||||
swap=0,
|
||||
deleted_at=None,
|
||||
deleted=0,
|
||||
created_at=None, flavorid=1,
|
||||
is_public=True, vcpu_weight=None,
|
||||
id=2, disabled=False, rxtx_factor=1.0)
|
||||
flavor = objects.Flavor(
|
||||
id=2,
|
||||
name='m1.small',
|
||||
memory_mb=128,
|
||||
vcpus=4,
|
||||
root_gb=0,
|
||||
ephemeral_gb=0,
|
||||
swap=0,
|
||||
deleted_at=None,
|
||||
deleted=0,
|
||||
created_at=None, flavorid=1,
|
||||
is_public=True, vcpu_weight=None,
|
||||
disabled=False,
|
||||
extra_specs={})
|
||||
conf = d.get_base_config(None, 'ca:fe:de:ad:be:ef', image_meta,
|
||||
flavor, 'kvm', 'normal')
|
||||
self.assertEqual(4, conf.vhost_queues)
|
||||
@ -728,7 +731,8 @@ class LibvirtVifTestCase(test.NoDBTestCase):
|
||||
self.flags(tx_queue_size=1024, group='libvirt')
|
||||
v = vif.LibvirtGenericVIFDriver()
|
||||
conf = v.get_base_config(
|
||||
None, 'ca:fe:de:ad:be:ef', {}, objects.Flavor(), 'kvm', vnic_type)
|
||||
None, 'ca:fe:de:ad:be:ef', {},
|
||||
objects.Flavor(vcpus=2), 'kvm', vnic_type)
|
||||
return v, conf
|
||||
|
||||
def test_virtio_vhost_queue_sizes(self):
|
||||
@ -874,8 +878,22 @@ class LibvirtVifTestCase(test.NoDBTestCase):
|
||||
d = vif.LibvirtGenericVIFDriver()
|
||||
image_meta = {'properties': {'os_name': 'fedora22'}}
|
||||
image_meta = objects.ImageMeta.from_dict(image_meta)
|
||||
flavor = objects.Flavor(
|
||||
id=2,
|
||||
name='m1.small',
|
||||
memory_mb=128,
|
||||
vcpus=4,
|
||||
root_gb=0,
|
||||
ephemeral_gb=0,
|
||||
swap=0,
|
||||
deleted_at=None,
|
||||
deleted=0,
|
||||
created_at=None, flavorid=1,
|
||||
is_public=True, vcpu_weight=None,
|
||||
disabled=False,
|
||||
extra_specs={})
|
||||
d.get_base_config(None, 'ca:fe:de:ad:be:ef', image_meta,
|
||||
None, 'kvm', 'normal')
|
||||
flavor, 'kvm', 'normal')
|
||||
mock_set.assert_called_once_with(mock.ANY, 'ca:fe:de:ad:be:ef',
|
||||
'virtio', None, None, None)
|
||||
|
||||
@ -1089,9 +1107,9 @@ class LibvirtVifTestCase(test.NoDBTestCase):
|
||||
mq_ins = objects.Instance(
|
||||
id=1, uuid='f0000000-0000-0000-0000-000000000001',
|
||||
image_ref=uuids.image_ref, flavor=self.flavor_2vcpu,
|
||||
project_id=723, system_metadata={
|
||||
system_metadata={
|
||||
'image_hw_vif_multiqueue_enabled': 'True'
|
||||
}
|
||||
},
|
||||
)
|
||||
d2.plug(mq_ins, self.vif_tap)
|
||||
mock_create_tap_dev.assert_called_once_with(
|
||||
@ -1129,9 +1147,10 @@ class LibvirtVifTestCase(test.NoDBTestCase):
|
||||
ins = objects.Instance(
|
||||
id=1, uuid='f0000000-0000-0000-0000-000000000001',
|
||||
image_ref=uuids.image_ref, flavor=self.flavor_2vcpu,
|
||||
project_id=723, system_metadata={
|
||||
project_id=723,
|
||||
system_metadata={
|
||||
'image_hw_vif_multiqueue_enabled': 'True'
|
||||
}
|
||||
},
|
||||
)
|
||||
d1.plug(ins, self.vif_tap)
|
||||
mock_create_tap_dev.assert_called_once_with(
|
||||
@ -1147,10 +1166,11 @@ class LibvirtVifTestCase(test.NoDBTestCase):
|
||||
ins = objects.Instance(
|
||||
id=1, uuid='f0000000-0000-0000-0000-000000000001',
|
||||
image_ref=uuids.image_ref, flavor=self.flavor_2vcpu,
|
||||
project_id=723, system_metadata={
|
||||
project_id=723,
|
||||
system_metadata={
|
||||
'image_hw_vif_multiqueue_enabled': 'True',
|
||||
'image_hw_vif_model': 'e1000',
|
||||
}
|
||||
},
|
||||
)
|
||||
d1.plug(ins, self.vif_tap)
|
||||
mock_create_tap_dev.assert_called_once_with(
|
||||
|
@ -5452,6 +5452,50 @@ class PCINUMAAffinityPolicyTest(test.NoDBTestCase):
|
||||
image_meta.properties.hw_pci_numa_affinity_policy = "fake"
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class VIFMultiqueueEnabledTest(test.NoDBTestCase):
|
||||
|
||||
@ddt.unpack
|
||||
@ddt.data(
|
||||
# pass: no configuration
|
||||
(None, None, False),
|
||||
# pass: flavor-only configuration
|
||||
('yes', None, True),
|
||||
# pass: image-only configuration
|
||||
(None, False, False),
|
||||
# pass: identical image and flavor configuration
|
||||
('yes', True, True),
|
||||
# fail: mismatched image and flavor configuration
|
||||
('no', True, exception.FlavorImageConflict),
|
||||
)
|
||||
def test_get_vif_multiqueue_constraint(
|
||||
self, flavor_policy, image_policy, expected,
|
||||
):
|
||||
extra_specs = {}
|
||||
|
||||
if flavor_policy:
|
||||
extra_specs['hw:vif_multiqueue_enabled'] = flavor_policy
|
||||
|
||||
image_meta_props = {}
|
||||
|
||||
if image_policy:
|
||||
image_meta_props['hw_vif_multiqueue_enabled'] = image_policy
|
||||
|
||||
flavor = objects.Flavor(
|
||||
name='foo', vcpus=2, memory_mb=1024, extra_specs=extra_specs)
|
||||
image_meta = objects.ImageMeta.from_dict(
|
||||
{'name': 'bar', 'properties': image_meta_props})
|
||||
|
||||
if isinstance(expected, type) and issubclass(expected, Exception):
|
||||
self.assertRaises(
|
||||
expected, hw.get_vif_multiqueue_constraint, flavor, image_meta,
|
||||
)
|
||||
else:
|
||||
self.assertEqual(
|
||||
expected, hw.get_vif_multiqueue_constraint(flavor, image_meta),
|
||||
)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class VTPMConfigTest(test.NoDBTestCase):
|
||||
|
||||
|
@ -1784,6 +1784,55 @@ def get_pci_numa_policy_constraint(
|
||||
return policy
|
||||
|
||||
|
||||
def get_vif_multiqueue_constraint(
|
||||
flavor: 'objects.Flavor',
|
||||
image_meta: 'objects.ImageMeta',
|
||||
) -> bool:
|
||||
"""Validate and return the requested VIF multiqueue configuration.
|
||||
|
||||
: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: True if the multiqueue must be enabled, else False.
|
||||
"""
|
||||
if flavor.vcpus < 2:
|
||||
return False
|
||||
|
||||
flavor_value_str, image_value = _get_flavor_image_meta(
|
||||
'vif_multiqueue_enabled', flavor, image_meta)
|
||||
|
||||
flavor_value = None
|
||||
if flavor_value_str is not None:
|
||||
flavor_value = strutils.bool_from_string(flavor_value_str)
|
||||
|
||||
if (
|
||||
image_value is not None and
|
||||
flavor_value is not None and
|
||||
image_value != flavor_value
|
||||
):
|
||||
msg = _(
|
||||
"Flavor %(flavor_name)s has %(prefix)s:%(key)s extra spec "
|
||||
"explicitly set to %(flavor_val)s, conflicting with image "
|
||||
"%(image_name)s which has %(prefix)s_%(key)s explicitly set to "
|
||||
"%(image_val)s."
|
||||
)
|
||||
raise exception.FlavorImageConflict(
|
||||
msg % {
|
||||
'prefix': 'hw',
|
||||
'key': 'vif_multiqueue_enabled',
|
||||
'flavor_name': flavor.name,
|
||||
'flavor_val': flavor_value,
|
||||
'image_name': image_meta.name,
|
||||
'image_val': image_value,
|
||||
}
|
||||
)
|
||||
|
||||
return flavor_value or image_value or False
|
||||
|
||||
|
||||
def get_vtpm_constraint(
|
||||
flavor: 'objects.Flavor',
|
||||
image_meta: 'objects.ImageMeta',
|
||||
|
@ -39,6 +39,7 @@ from nova.pci import utils as pci_utils
|
||||
import nova.privsep.linux_net
|
||||
from nova import profiler
|
||||
from nova import utils
|
||||
from nova.virt import hardware
|
||||
from nova.virt.libvirt import config as vconfig
|
||||
from nova.virt.libvirt import designer
|
||||
from nova.virt.libvirt import host as libvirt_host
|
||||
@ -256,9 +257,12 @@ class LibvirtGenericVIFDriver(object):
|
||||
"""A methods to set the number of virtio queues,
|
||||
if it has been requested in extra specs.
|
||||
"""
|
||||
if not isinstance(image_meta, objects.ImageMeta):
|
||||
image_meta = objects.ImageMeta.from_dict(image_meta)
|
||||
|
||||
driver = None
|
||||
vhost_queues = None
|
||||
if self._requests_multiqueue(image_meta):
|
||||
if hardware.get_vif_multiqueue_constraint(flavor, image_meta):
|
||||
driver = 'vhost'
|
||||
max_tap_queues = self._get_max_tap_queues()
|
||||
if max_tap_queues:
|
||||
@ -269,19 +273,6 @@ class LibvirtGenericVIFDriver(object):
|
||||
|
||||
return (driver, vhost_queues)
|
||||
|
||||
def _requests_multiqueue(self, image_meta):
|
||||
"""Check if multiqueue property is set in the image metadata."""
|
||||
|
||||
if not isinstance(image_meta, objects.ImageMeta):
|
||||
image_meta = objects.ImageMeta.from_dict(image_meta)
|
||||
|
||||
img_props = image_meta.properties
|
||||
|
||||
if img_props.get('hw_vif_multiqueue_enabled'):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _get_max_tap_queues(self):
|
||||
# Note(sean-k-mooney): some linux distros have backported
|
||||
# changes for newer kernels which make the kernel version
|
||||
@ -692,9 +683,10 @@ class LibvirtGenericVIFDriver(object):
|
||||
vif_model = self.get_vif_model(image_meta=image_meta)
|
||||
# TODO(ganso): explore whether multiqueue works for other vif models
|
||||
# that go through this code path.
|
||||
multiqueue = (instance.get_flavor().vcpus > 1 and
|
||||
self._requests_multiqueue(image_meta) and
|
||||
vif_model == network_model.VIF_MODEL_VIRTIO)
|
||||
multiqueue = False
|
||||
if vif_model == network_model.VIF_MODEL_VIRTIO:
|
||||
multiqueue = hardware.get_vif_multiqueue_constraint(
|
||||
instance.flavor, image_meta)
|
||||
nova.privsep.linux_net.create_tap_dev(dev, mac, multiqueue=multiqueue)
|
||||
network = vif.get('network')
|
||||
mtu = network.get_meta('mtu') if network else None
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
The ``hw:vif_multiqueue_enabled`` flavor extra spec has been added. This
|
||||
is a boolean option that, when set, can be used to enable or disable
|
||||
multiqueue for virtio-net VIFs. It complements the equivalent image
|
||||
metadata property, ``hw_vif_multiqueue_enabled``. If both values are set,
|
||||
they must be identical or an error will be raised.
|
Loading…
x
Reference in New Issue
Block a user