Merge "Packed virtqueue support was added."
This commit is contained in:
commit
8ac050c253
@ -4,5 +4,5 @@
|
||||
"hw_architecture": "x86_64"
|
||||
},
|
||||
"nova_object.name": "ImageMetaPropsPayload",
|
||||
"nova_object.version": "1.12"
|
||||
"nova_object.version": "1.13"
|
||||
}
|
||||
|
@ -527,6 +527,18 @@ feature_flag_validators = [
|
||||
'description': 'model for vIOMMU',
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='hw:virtio_packed_ring',
|
||||
description=(
|
||||
'Permit guests to negotiate the virtio packed ring format. '
|
||||
'This requires guest support and is only supported by '
|
||||
'the libvirt driver.'
|
||||
),
|
||||
value={
|
||||
'type': bool,
|
||||
'description': 'Whether to enable packed virtqueue',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
ephemeral_encryption_validators = [
|
||||
|
@ -751,6 +751,9 @@ class API:
|
||||
if flavor['memory_mb'] < int(image.get('min_ram') or 0):
|
||||
raise exception.FlavorMemoryTooSmall()
|
||||
|
||||
# Verify flavor/image Virtio Packed Ring configuration conflict.
|
||||
hardware.get_packed_virtqueue_constraint(flavor, image)
|
||||
|
||||
# Image min_disk is in gb, size is in bytes. For sanity, have them both
|
||||
# in bytes.
|
||||
image_min_disk = int(image.get('min_disk') or 0) * units.Gi
|
||||
|
@ -130,7 +130,8 @@ class ImageMetaPropsPayload(base.NotificationPayloadBase):
|
||||
# 'hw_ephemeral_encryption_format' fields
|
||||
# Version 1.11: Added 'hw_locked_memory' field
|
||||
# Version 1.12: Added 'hw_viommu_model' field
|
||||
VERSION = '1.12'
|
||||
# Version 1.13: Added 'hw_virtio_packed_ring' field
|
||||
VERSION = '1.13'
|
||||
|
||||
SCHEMA = {
|
||||
k: ('image_meta_props', k) for k in image_meta.ImageMetaProps.fields}
|
||||
|
@ -192,14 +192,17 @@ class ImageMetaProps(base.NovaObject):
|
||||
# 'hw_ephemeral_encryption_format' fields
|
||||
# Version 1.33: Added 'hw_locked_memory' field
|
||||
# Version 1.34: Added 'hw_viommu_model' field
|
||||
# Version 1.35: Added 'hw_virtio_packed_ring' field
|
||||
# NOTE(efried): When bumping this version, the version of
|
||||
# ImageMetaPropsPayload must also be bumped. See its docstring for details.
|
||||
VERSION = '1.34'
|
||||
VERSION = '1.35'
|
||||
|
||||
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, 35):
|
||||
primitive.pop('hw_virtio_packed_ring', None)
|
||||
if target_version < (1, 34):
|
||||
primitive.pop('hw_viommu_model', None)
|
||||
if target_version < (1, 33):
|
||||
@ -473,6 +476,9 @@ class ImageMetaProps(base.NovaObject):
|
||||
'hw_ephemeral_encryption_format':
|
||||
fields.BlockDeviceEncryptionFormatTypeField(),
|
||||
|
||||
# boolean - If true, this will enable the virtio packed ring feature
|
||||
'hw_virtio_packed_ring': fields.FlexibleBooleanField(),
|
||||
|
||||
# if true download using bittorrent
|
||||
'img_bittorrent': fields.FlexibleBooleanField(),
|
||||
|
||||
|
@ -271,6 +271,22 @@ def accelerators_filter(ctxt, request_spec):
|
||||
return True
|
||||
|
||||
|
||||
@trace_request_filter
|
||||
def packed_virtqueue_filter(ctxt, request_spec):
|
||||
"""Allow only compute nodes with Packed virtqueue.
|
||||
|
||||
This filter retains only nodes whose compute manager published the
|
||||
COMPUTE_NET_VIRTIO_PACKED trait, thus indicates virtqueue packed feature.
|
||||
"""
|
||||
trait_name = os_traits.COMPUTE_NET_VIRTIO_PACKED
|
||||
if (hardware.get_packed_virtqueue_constraint(request_spec.flavor,
|
||||
request_spec.image)):
|
||||
request_spec.root_required.add(trait_name)
|
||||
LOG.debug('virtqueue_filter request filter added required '
|
||||
'trait %s', trait_name)
|
||||
return True
|
||||
|
||||
|
||||
@trace_request_filter
|
||||
def routed_networks_filter(
|
||||
ctxt: nova_context.RequestContext,
|
||||
@ -436,6 +452,7 @@ ALL_REQUEST_FILTERS = [
|
||||
isolate_aggregates,
|
||||
transform_image_metadata,
|
||||
accelerators_filter,
|
||||
packed_virtqueue_filter,
|
||||
routed_networks_filter,
|
||||
remote_managed_ports_filter,
|
||||
ephemeral_encryption_filter,
|
||||
|
@ -18,6 +18,7 @@ Provides common functionality for integrated unit tests
|
||||
"""
|
||||
|
||||
import collections
|
||||
import datetime
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
@ -394,6 +395,32 @@ class InstanceHelperMixin:
|
||||
|
||||
return flavor['id']
|
||||
|
||||
def _create_image(self, metadata):
|
||||
image = {
|
||||
'id': 'c456eb30-91d7-4f43-8f46-2efd9eccd744',
|
||||
'name': 'fake-image-custom-property',
|
||||
'created_at': datetime.datetime(2011, 1, 1, 1, 2, 3),
|
||||
'updated_at': datetime.datetime(2011, 1, 1, 1, 2, 3),
|
||||
'deleted_at': None,
|
||||
'deleted': False,
|
||||
'status': 'active',
|
||||
'is_public': False,
|
||||
'container_format': 'raw',
|
||||
'disk_format': 'raw',
|
||||
'size': '25165824',
|
||||
'min_ram': 0,
|
||||
'min_disk': 0,
|
||||
'protected': False,
|
||||
'visibility': 'public',
|
||||
'tags': ['tag1', 'tag2'],
|
||||
'properties': {
|
||||
'kernel_id': 'nokernel',
|
||||
'ramdisk_id': 'nokernel',
|
||||
},
|
||||
}
|
||||
image['properties'].update(metadata)
|
||||
return self.glance.create(None, image)
|
||||
|
||||
def _build_server(self, name=None, image_uuid=None, flavor_id=None,
|
||||
networks=None, az=None, host=None):
|
||||
"""Build a request for the server create API.
|
||||
|
@ -1238,7 +1238,7 @@ class TestInstanceNotificationSample(
|
||||
'nova_object.data': {},
|
||||
'nova_object.name': 'ImageMetaPropsPayload',
|
||||
'nova_object.namespace': 'nova',
|
||||
'nova_object.version': '1.12',
|
||||
'nova_object.version': '1.13',
|
||||
},
|
||||
'image.size': 58145823,
|
||||
'image.tags': [],
|
||||
@ -1334,7 +1334,7 @@ class TestInstanceNotificationSample(
|
||||
'nova_object.data': {},
|
||||
'nova_object.name': 'ImageMetaPropsPayload',
|
||||
'nova_object.namespace': 'nova',
|
||||
'nova_object.version': '1.12',
|
||||
'nova_object.version': '1.13',
|
||||
},
|
||||
'image.size': 58145823,
|
||||
'image.tags': [],
|
||||
|
@ -2188,6 +2188,77 @@ class ServerMovingTests(integrated_helpers.ProviderUsageBaseTestCase):
|
||||
self.assert_hypervisor_usage(
|
||||
dest_rp_uuid, self.flavor2, volume_backed=False)
|
||||
|
||||
def test_resize_server_conflict(self):
|
||||
|
||||
# Set appropriate traits for Resource Provider
|
||||
rp_uuid1 = self._get_provider_uuid_by_host(self.compute1.host)
|
||||
self._set_provider_traits(rp_uuid1, ['COMPUTE_NET_VIRTIO_PACKED'])
|
||||
|
||||
# Create image
|
||||
image = self._create_image(metadata={'hw_virtio_packed_ring': 'true'})
|
||||
|
||||
# Create server
|
||||
server = self._build_server(image_uuid=image['id'], networks='none')
|
||||
created_server = self.api.post_server({"server": server})
|
||||
created_server_id = created_server['id']
|
||||
found_server = self._wait_for_state_change(created_server, 'ACTIVE')
|
||||
|
||||
# Create a flavor with conflict in relation to the image configuration
|
||||
flavor_id = self._create_flavor(
|
||||
extra_spec={'hw:virtio_packed_ring': 'false'})
|
||||
|
||||
# Resize server(flavorRef: 1 -> 2)
|
||||
post = {'resize': {"flavorRef": flavor_id}}
|
||||
|
||||
ex = self.assertRaises(client.OpenStackApiException,
|
||||
self.api.post_server_action,
|
||||
created_server_id, post)
|
||||
|
||||
# By returning 400, We want to confirm that the RESIZE server
|
||||
# does not cause unexpected behavior.
|
||||
self.assertEqual(400, ex.response.status_code)
|
||||
|
||||
# Verify that the instance is still in the Active state
|
||||
self.assertEqual('ACTIVE', found_server['status'])
|
||||
|
||||
# Cleanup
|
||||
self._delete_server(found_server)
|
||||
|
||||
def test_rebuild_server_conflict(self):
|
||||
|
||||
# Set appropriate traits for Resource Provider
|
||||
rp_uuid1 = self._get_provider_uuid_by_host(self.compute1.host)
|
||||
self._set_provider_traits(rp_uuid1, ['COMPUTE_NET_VIRTIO_PACKED'])
|
||||
|
||||
# Create flavor
|
||||
flavor_id = self._create_flavor(
|
||||
extra_spec={'hw:virtio_packed_ring': 'true'})
|
||||
|
||||
# Create server
|
||||
server = self._build_server(flavor_id=flavor_id, networks='none')
|
||||
created_server = self.api.post_server({"server": server})
|
||||
created_server_id = created_server['id']
|
||||
found_server = self._wait_for_state_change(created_server, 'ACTIVE')
|
||||
|
||||
# Create an image with conflict in relation to the flavor configuration
|
||||
image = self._create_image(metadata={'hw_virtio_packed_ring': 'false'})
|
||||
|
||||
# Now rebuild the server with a different image
|
||||
post = {'rebuild': {'imageRef': image['id']}}
|
||||
ex = self.assertRaises(client.OpenStackApiException,
|
||||
self.api.post_server_action,
|
||||
created_server_id, post)
|
||||
|
||||
# By returning 400, We want to confirm that the RESIZE server
|
||||
# does not cause unexpected behavior.
|
||||
self.assertEqual(400, ex.response.status_code)
|
||||
|
||||
# Verify that the instance is still in the Active state
|
||||
self.assertEqual('ACTIVE', found_server['status'])
|
||||
|
||||
# Cleanup
|
||||
self._delete_server(found_server)
|
||||
|
||||
def test_evacuate_with_no_compute(self):
|
||||
source_hostname = self.compute1.host
|
||||
dest_hostname = self.compute2.host
|
||||
|
@ -386,7 +386,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.12-b9c64832d7772c1973e913bacbe0e8f9',
|
||||
'ImageMetaPropsPayload': '1.13-24345c28a6463e85e12902d43af0ecf2',
|
||||
'InstanceActionNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
||||
'InstanceActionPayload': '1.8-4fa3da9cbf0761f1f700ae578f36dc2f',
|
||||
'InstanceActionRebuildNotification':
|
||||
|
@ -1105,7 +1105,7 @@ object_data = {
|
||||
'HyperVLiveMigrateData': '1.4-e265780e6acfa631476c8170e8d6fce0',
|
||||
'IDEDeviceBus': '1.0-29d4c9f27ac44197f01b6ac1b7e16502',
|
||||
'ImageMeta': '1.8-642d1b2eb3e880a367f37d72dd76162d',
|
||||
'ImageMetaProps': '1.34-29b3a6b7fe703f36bfd240d914f16c21',
|
||||
'ImageMetaProps': '1.35-66ec4135a4c08d6e67e39cb0400b059e',
|
||||
'Instance': '2.8-2727dba5e4a078e6cc848c1f94f7eb24',
|
||||
'InstanceAction': '1.2-9a5abc87fdd3af46f45731960651efb5',
|
||||
'InstanceActionEvent': '1.4-5b1f361bd81989f8bb2c20bb7e8a4cb4',
|
||||
|
@ -499,6 +499,41 @@ class TestRequestFilter(test.NoDBTestCase):
|
||||
# Assert about logging
|
||||
mock_log.assert_not_called()
|
||||
|
||||
@mock.patch.object(request_filter, 'LOG')
|
||||
def test_virtio_filter_with_packed_ring_in_flavor(self, mock_log):
|
||||
# First ensure that packed_virtqueue_filter is included
|
||||
self.assertIn(request_filter.packed_virtqueue_filter,
|
||||
request_filter.ALL_REQUEST_FILTERS)
|
||||
|
||||
es = {'hw:virtio_packed_ring': 'true'}
|
||||
reqspec = objects.RequestSpec(
|
||||
flavor=objects.Flavor(extra_specs=es),
|
||||
image=objects.ImageMeta(properties=objects.ImageMetaProps()))
|
||||
self.assertEqual(set(), reqspec.root_required)
|
||||
self.assertEqual(set(), reqspec.root_forbidden)
|
||||
|
||||
# Request filter puts the trait into the request spec
|
||||
request_filter.packed_virtqueue_filter(self.context, reqspec)
|
||||
self.assertEqual({ot.COMPUTE_NET_VIRTIO_PACKED}, reqspec.root_required)
|
||||
self.assertEqual(set(), reqspec.root_forbidden)
|
||||
|
||||
@mock.patch.object(request_filter, 'LOG')
|
||||
def test_virtio_filter_with_packed_ring_in_image(self, mock_log):
|
||||
# First ensure that packed_virtqueue_filter is included
|
||||
self.assertIn(request_filter.packed_virtqueue_filter,
|
||||
request_filter.ALL_REQUEST_FILTERS)
|
||||
|
||||
reqspec = objects.RequestSpec(flavor=objects.Flavor(extra_specs={}),
|
||||
image=objects.ImageMeta(
|
||||
properties=objects.ImageMetaProps(hw_virtio_packed_ring=True)))
|
||||
self.assertEqual(set(), reqspec.root_required)
|
||||
self.assertEqual(set(), reqspec.root_forbidden)
|
||||
|
||||
# Request filter puts the trait into the request spec
|
||||
request_filter.packed_virtqueue_filter(self.context, reqspec)
|
||||
self.assertEqual({ot.COMPUTE_NET_VIRTIO_PACKED}, reqspec.root_required)
|
||||
self.assertEqual(set(), reqspec.root_forbidden)
|
||||
|
||||
def test_routed_networks_filter_not_enabled(self):
|
||||
self.assertIn(request_filter.routed_networks_filter,
|
||||
request_filter.ALL_REQUEST_FILTERS)
|
||||
|
@ -1923,6 +1923,26 @@ class LibvirtConfigGuestInterfaceTest(LibvirtConfigBaseTest):
|
||||
self.assertTrue(obj.uses_virtio)
|
||||
return obj
|
||||
|
||||
def test_config_driver_packed_options(self):
|
||||
obj = self._get_virtio_interface()
|
||||
obj.driver_name = "vhost"
|
||||
obj.driver_packed = True
|
||||
|
||||
xml = obj.to_xml()
|
||||
self.assertXmlEqual(xml, """
|
||||
<interface type="ethernet">
|
||||
<mac address="DE:AD:BE:EF:CA:FE"/>
|
||||
<model type="virtio"/>
|
||||
<driver name="vhost" packed="on"/>
|
||||
<target dev="vnet0"/>
|
||||
</interface>""")
|
||||
|
||||
# parse the xml from the first object into a new object and make sure
|
||||
# they are the same
|
||||
obj2 = config.LibvirtConfigGuestInterface()
|
||||
obj2.parse_str(xml)
|
||||
self.assertXmlEqual(xml, obj2.to_xml())
|
||||
|
||||
def test_config_driver_options(self):
|
||||
obj = self._get_virtio_interface()
|
||||
obj.driver_name = "vhost"
|
||||
|
@ -40,7 +40,7 @@ class DesignerTestCase(test.NoDBTestCase):
|
||||
conf = config.LibvirtConfigGuestInterface()
|
||||
designer.set_vif_guest_frontend_config(conf, 'fake-mac',
|
||||
'fake-model', 'fake-driver',
|
||||
'fake-queues', None)
|
||||
'fake-queues', None, None)
|
||||
self.assertEqual('fake-mac', conf.mac_addr)
|
||||
self.assertEqual('fake-model', conf.model)
|
||||
self.assertEqual('fake-driver', conf.driver_name)
|
||||
@ -51,7 +51,7 @@ class DesignerTestCase(test.NoDBTestCase):
|
||||
conf = config.LibvirtConfigGuestInterface()
|
||||
designer.set_vif_guest_frontend_config(conf, 'fake-mac',
|
||||
'fake-model', 'fake-driver',
|
||||
'fake-queues', 1024)
|
||||
'fake-queues', 1024, None)
|
||||
self.assertEqual('fake-mac', conf.mac_addr)
|
||||
self.assertEqual('fake-model', conf.model)
|
||||
self.assertEqual('fake-driver', conf.driver_name)
|
||||
|
@ -992,6 +992,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
expected = {
|
||||
'COMPUTE_GRAPHICS_MODEL_VGA': True,
|
||||
'COMPUTE_NET_VIF_MODEL_VIRTIO': True,
|
||||
'COMPUTE_NET_VIRTIO_PACKED': True,
|
||||
'COMPUTE_SECURITY_TPM_1_2': False,
|
||||
'COMPUTE_SECURITY_TPM_2_0': False,
|
||||
'COMPUTE_STORAGE_BUS_VIRTIO': True,
|
||||
@ -1030,7 +1031,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver, '_get_storage_bus_traits')
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver, '_get_video_model_traits')
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver, '_get_vif_model_traits')
|
||||
def test_static_traits__invalid_trait(
|
||||
def test_static_traits_invalid_trait(
|
||||
self, mock_vif_traits, mock_video_traits, mock_storage_traits,
|
||||
mock_cpu_traits, mock_log,
|
||||
):
|
||||
@ -1043,6 +1044,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
|
||||
expected = {
|
||||
'COMPUTE_NET_VIF_MODEL_VIRTIO': True,
|
||||
'COMPUTE_NET_VIRTIO_PACKED': True,
|
||||
'COMPUTE_SECURITY_TPM_1_2': False,
|
||||
'COMPUTE_SECURITY_TPM_2_0': False,
|
||||
'COMPUTE_VIOMMU_MODEL_AUTO': True,
|
||||
|
@ -725,6 +725,38 @@ class LibvirtVifTestCase(test.NoDBTestCase):
|
||||
self.assertEqual(4, conf.vhost_queues)
|
||||
self.assertIsNone(conf.driver_name)
|
||||
|
||||
def _test_virtio_packed_config(self, image_meta, flavor):
|
||||
d = vif.LibvirtGenericVIFDriver()
|
||||
|
||||
xml = self._get_instance_xml(d, self.vif_bridge,
|
||||
image_meta, flavor)
|
||||
|
||||
node = self._get_node(xml)
|
||||
packed = node.find("driver").get("packed")
|
||||
self.assertEqual(packed, 'on')
|
||||
|
||||
def test_image_packed_config(self):
|
||||
extra_specs = {}
|
||||
extra_specs['hw:virtio_packed_ring'] = True
|
||||
|
||||
flavor = objects.Flavor(
|
||||
name='foo', vcpus=2, memory_mb=1024, extra_specs=extra_specs)
|
||||
image_meta = objects.ImageMeta.from_dict(
|
||||
{'name': 'bar', 'properties': {}})
|
||||
|
||||
self._test_virtio_packed_config(image_meta, flavor)
|
||||
|
||||
def test_flavor_packed_config(self):
|
||||
image_meta_props = {}
|
||||
image_meta_props['hw_virtio_packed_ring'] = True
|
||||
|
||||
flavor = objects.Flavor(
|
||||
name='foo', vcpus=2, memory_mb=1024, extra_specs={})
|
||||
image_meta = objects.ImageMeta.from_dict(
|
||||
{'name': 'bar', 'properties': image_meta_props})
|
||||
|
||||
self._test_virtio_packed_config(image_meta, flavor)
|
||||
|
||||
def _test_virtio_config_queue_sizes(
|
||||
self, vnic_type=network_model.VNIC_TYPE_NORMAL):
|
||||
self.flags(rx_queue_size=512, group='libvirt')
|
||||
@ -895,7 +927,7 @@ class LibvirtVifTestCase(test.NoDBTestCase):
|
||||
d.get_base_config(None, 'ca:fe:de:ad:be:ef', image_meta,
|
||||
flavor, 'kvm', 'normal')
|
||||
mock_set.assert_called_once_with(mock.ANY, 'ca:fe:de:ad:be:ef',
|
||||
'virtio', None, None, None)
|
||||
'virtio', None, None, None, False)
|
||||
|
||||
@mock.patch.object(vif.designer, 'set_vif_guest_frontend_config',
|
||||
wraps=vif.designer.set_vif_guest_frontend_config)
|
||||
@ -911,9 +943,9 @@ class LibvirtVifTestCase(test.NoDBTestCase):
|
||||
image_meta = objects.ImageMeta.from_dict(
|
||||
{'properties': {'hw_vif_model': 'virtio'}})
|
||||
conf = d.get_base_config(None, 'ca:fe:de:ad:be:ef', image_meta,
|
||||
None, 'kvm', vnic_type)
|
||||
objects.Flavor(vcpus=2), 'kvm', vnic_type)
|
||||
mock_set.assert_called_once_with(mock.ANY, 'ca:fe:de:ad:be:ef',
|
||||
None, None, None, None)
|
||||
None, None, None, None, False)
|
||||
self.assertIsNone(conf.vhost_queues)
|
||||
self.assertIsNone(conf.driver_name)
|
||||
self.assertIsNone(conf.model)
|
||||
|
@ -5701,11 +5701,57 @@ class VIFMultiqueueEnabledTest(test.NoDBTestCase):
|
||||
|
||||
if isinstance(expected, type) and issubclass(expected, Exception):
|
||||
self.assertRaises(
|
||||
expected, hw.get_vif_multiqueue_constraint, flavor, image_meta,
|
||||
expected, hw.get_vif_multiqueue_constraint, flavor, image_meta
|
||||
)
|
||||
else:
|
||||
self.assertEqual(
|
||||
expected, hw.get_vif_multiqueue_constraint(flavor, image_meta),
|
||||
expected, hw.get_vif_multiqueue_constraint(flavor, image_meta)
|
||||
)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class VIFVirtioEnabledTest(test.NoDBTestCase):
|
||||
|
||||
@ddt.unpack
|
||||
@ddt.data(
|
||||
# pass: no configuration
|
||||
(None, None, False),
|
||||
# pass: flavor-only configuration
|
||||
('yes', None, True),
|
||||
# pass: image-only configuration
|
||||
(None, True, True),
|
||||
# pass: identical image and flavor configuration
|
||||
('yes', True, True),
|
||||
# fail: mismatched image and flavor configuration
|
||||
('no', True, exception.FlavorImageConflict),
|
||||
)
|
||||
def test_get_vif_virtio_constraint(
|
||||
self, flavor_policy, image_policy, expected,
|
||||
):
|
||||
extra_specs = {}
|
||||
|
||||
if flavor_policy:
|
||||
extra_specs['hw:virtio_packed_ring'] = flavor_policy
|
||||
|
||||
image_meta_props = {}
|
||||
|
||||
if image_policy:
|
||||
image_meta_props['hw_virtio_packed_ring'] = 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_packed_virtqueue_constraint,
|
||||
flavor, image_meta,
|
||||
)
|
||||
else:
|
||||
self.assertEqual(
|
||||
expected, hw.get_packed_virtqueue_constraint(
|
||||
flavor, image_meta),
|
||||
)
|
||||
|
||||
|
||||
|
@ -1941,6 +1941,56 @@ def get_vif_multiqueue_constraint(
|
||||
return flavor_value or image_value or False
|
||||
|
||||
|
||||
def get_packed_virtqueue_constraint(
|
||||
flavor,
|
||||
image_meta,
|
||||
) -> bool:
|
||||
"""Validate and return the requested Packed virtqueue configuration.
|
||||
|
||||
:param flavor: ``nova.objects.Flavor`` or dict instance
|
||||
:param image_meta: ``nova.objects.ImageMeta`` or dict instance
|
||||
:raises: nova.exception.FlavorImageConflict if a value is specified in both
|
||||
the flavor and the image, but the values do not match
|
||||
:returns: True if the Packed virtqueue must be enabled, else False.
|
||||
"""
|
||||
key_value = 'virtio_packed_ring'
|
||||
|
||||
if type(image_meta) is dict:
|
||||
flavor_key = ':'.join(['hw', key_value])
|
||||
image_key = '_'.join(['hw', key_value])
|
||||
flavor_value_str = flavor.get('extra_specs', {}).get(flavor_key, None)
|
||||
image_value = image_meta.get('properties', {}).get(image_key, None)
|
||||
else:
|
||||
flavor_value_str, image_value = _get_flavor_image_meta(
|
||||
key_value, 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 has %(prefix)s:%(key)s extra spec "
|
||||
"explicitly set to %(flavor_val)s, conflicting with image "
|
||||
"which has %(prefix)s_%(key)s explicitly set to "
|
||||
"%(image_val)s."
|
||||
)
|
||||
raise exception.FlavorImageConflict(
|
||||
msg % {
|
||||
'prefix': 'hw',
|
||||
'key': key_value,
|
||||
'flavor_val': flavor_value,
|
||||
'image_val': image_value,
|
||||
}
|
||||
)
|
||||
|
||||
return flavor_value or image_value or False
|
||||
|
||||
|
||||
def get_vtpm_constraint(
|
||||
flavor: 'objects.Flavor',
|
||||
image_meta: 'objects.ImageMeta',
|
||||
|
@ -1765,6 +1765,7 @@ class LibvirtConfigGuestInterface(LibvirtConfigGuestDevice):
|
||||
self.filterparams = []
|
||||
self.driver_name = None
|
||||
self.driver_iommu = False
|
||||
self.driver_packed = False
|
||||
self.vhostuser_mode = None
|
||||
self.vhostuser_path = None
|
||||
self.vhostuser_type = None
|
||||
@ -1817,6 +1818,7 @@ class LibvirtConfigGuestInterface(LibvirtConfigGuestDevice):
|
||||
drv_elem = None
|
||||
if (self.driver_name or
|
||||
self.driver_iommu or
|
||||
self.driver_packed or
|
||||
self.net_type == "vhostuser"):
|
||||
|
||||
drv_elem = etree.Element("driver")
|
||||
@ -1825,6 +1827,8 @@ class LibvirtConfigGuestInterface(LibvirtConfigGuestDevice):
|
||||
drv_elem.set("name", self.driver_name)
|
||||
if self.driver_iommu:
|
||||
drv_elem.set("iommu", "on")
|
||||
if self.driver_packed:
|
||||
drv_elem.set("packed", "on")
|
||||
|
||||
if drv_elem is not None:
|
||||
if self.vhost_queues is not None:
|
||||
@ -1837,7 +1841,8 @@ class LibvirtConfigGuestInterface(LibvirtConfigGuestDevice):
|
||||
if (drv_elem.get('name') or drv_elem.get('queues') or
|
||||
drv_elem.get('rx_queue_size') or
|
||||
drv_elem.get('tx_queue_size') or
|
||||
drv_elem.get('iommu')):
|
||||
drv_elem.get('iommu') or
|
||||
drv_elem.get('packed')):
|
||||
# Append the driver element into the dom only if name
|
||||
# or queues or tx/rx or iommu attributes are set.
|
||||
dev.append(drv_elem)
|
||||
@ -1937,6 +1942,7 @@ class LibvirtConfigGuestInterface(LibvirtConfigGuestDevice):
|
||||
elif c.tag == 'driver':
|
||||
self.driver_name = c.get('name')
|
||||
self.driver_iommu = (c.get('iommu', '') == 'on')
|
||||
self.driver_packed = (c.get('packed', '') == 'on')
|
||||
self.vhost_queues = c.get('queues')
|
||||
self.vhost_rx_queue_size = c.get('rx_queue_size')
|
||||
self.vhost_tx_queue_size = c.get('tx_queue_size')
|
||||
|
@ -24,7 +24,7 @@ from nova.pci import utils as pci_utils
|
||||
|
||||
|
||||
def set_vif_guest_frontend_config(conf, mac, model, driver, queues,
|
||||
rx_queue_size):
|
||||
rx_queue_size, packed):
|
||||
"""Populate a LibvirtConfigGuestInterface instance
|
||||
with guest frontend details.
|
||||
|
||||
@ -39,6 +39,8 @@ def set_vif_guest_frontend_config(conf, mac, model, driver, queues,
|
||||
conf.vhost_queues = queues
|
||||
if rx_queue_size:
|
||||
conf.vhost_rx_queue_size = rx_queue_size
|
||||
if packed is not None:
|
||||
conf.driver_packed = packed
|
||||
|
||||
|
||||
def set_vif_host_backend_ethernet_config(conf, tapname):
|
||||
|
@ -224,17 +224,14 @@ NEXT_MIN_QEMU_VERSION = (6, 2, 0)
|
||||
# vIOMMU model value `virtio` minimal support version
|
||||
MIN_LIBVIRT_VIOMMU_VIRTIO_MODEL = (8, 3, 0)
|
||||
|
||||
|
||||
MIN_LIBVIRT_TB_CACHE_SIZE = (8, 0, 0)
|
||||
|
||||
# Virtuozzo driver support
|
||||
MIN_VIRTUOZZO_VERSION = (7, 0, 0)
|
||||
|
||||
|
||||
# Names of the types that do not get compressed during migration
|
||||
NO_COMPRESSION_TYPES = ('qcow2',)
|
||||
|
||||
|
||||
# number of serial console limit
|
||||
QEMU_MAX_SERIAL_PORTS = 4
|
||||
# Qemu supports 4 serial consoles, we remove 1 because of the PTY one defined
|
||||
@ -244,7 +241,6 @@ VGPU_RESOURCE_SEMAPHORE = 'vgpu_resources'
|
||||
|
||||
LIBVIRT_PERF_EVENT_PREFIX = 'VIR_PERF_PARAM_'
|
||||
|
||||
|
||||
# Maxphysaddr minimal support version.
|
||||
MIN_LIBVIRT_MAXPHYSADDR = (8, 7, 0)
|
||||
MIN_QEMU_MAXPHYSADDR = (2, 7, 0)
|
||||
@ -9041,6 +9037,7 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
|
||||
traits: ty.Dict[str, bool] = {}
|
||||
traits.update(self._get_cpu_traits())
|
||||
traits.update(self._get_packed_virtqueue_traits())
|
||||
traits.update(self._get_storage_bus_traits())
|
||||
traits.update(self._get_video_model_traits())
|
||||
traits.update(self._get_vif_model_traits())
|
||||
@ -12423,6 +12420,14 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
in supported_models for model in all_models
|
||||
}
|
||||
|
||||
def _get_packed_virtqueue_traits(self) -> ty.Dict[str, bool]:
|
||||
"""Get Virtio Packed Ring traits to be set on the host's
|
||||
resource provider.
|
||||
|
||||
:return: A dict of trait names mapped to boolean values.
|
||||
"""
|
||||
return {ot.COMPUTE_NET_VIRTIO_PACKED: True}
|
||||
|
||||
def _get_cpu_traits(self) -> ty.Dict[str, bool]:
|
||||
"""Get CPU-related traits to be set and unset on the host's resource
|
||||
provider.
|
||||
|
@ -192,11 +192,13 @@ class LibvirtGenericVIFDriver(object):
|
||||
vhost_queues = None
|
||||
rx_queue_size = None
|
||||
|
||||
packed = self._get_packed_virtqueue_settings(
|
||||
image_meta, flavor)
|
||||
# NOTE(stephenfin): Skip most things here as only apply to virtio
|
||||
# devices
|
||||
if vnic_type in network_model.VNIC_TYPES_DIRECT_PASSTHROUGH:
|
||||
designer.set_vif_guest_frontend_config(
|
||||
conf, mac, model, driver, vhost_queues, rx_queue_size)
|
||||
conf, mac, model, driver, vhost_queues, rx_queue_size, packed)
|
||||
return conf
|
||||
|
||||
rx_queue_size = CONF.libvirt.rx_queue_size
|
||||
@ -211,7 +213,7 @@ class LibvirtGenericVIFDriver(object):
|
||||
# The rest of this only applies to virtio
|
||||
if model != network_model.VIF_MODEL_VIRTIO:
|
||||
designer.set_vif_guest_frontend_config(
|
||||
conf, mac, model, driver, vhost_queues, rx_queue_size)
|
||||
conf, mac, model, driver, vhost_queues, rx_queue_size, packed)
|
||||
return conf
|
||||
|
||||
# Workaround libvirt bug, where it mistakenly enables vhost mode, even
|
||||
@ -243,7 +245,7 @@ class LibvirtGenericVIFDriver(object):
|
||||
driver = 'vhost'
|
||||
|
||||
designer.set_vif_guest_frontend_config(
|
||||
conf, mac, model, driver, vhost_queues, rx_queue_size)
|
||||
conf, mac, model, driver, vhost_queues, rx_queue_size, packed)
|
||||
|
||||
return conf
|
||||
|
||||
@ -296,6 +298,13 @@ class LibvirtGenericVIFDriver(object):
|
||||
else:
|
||||
return None
|
||||
|
||||
def _get_packed_virtqueue_settings(self, image_meta, flavor):
|
||||
"""A method to check if Virtio Packed Ring was requested."""
|
||||
if not isinstance(image_meta, objects.ImageMeta):
|
||||
image_meta = objects.ImageMeta.from_dict(image_meta)
|
||||
|
||||
return hardware.get_packed_virtqueue_constraint(flavor, image_meta)
|
||||
|
||||
def get_bridge_name(self, vif):
|
||||
return vif['network']['bridge']
|
||||
|
||||
|
@ -0,0 +1,19 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Handling packed virtqueue requests for an instance is now supported on
|
||||
the nodes with Qemu v4.2 and Libvirt v6.3.
|
||||
|
||||
VMs using virtio-net will see an increase in performance. The increase
|
||||
can be anywhere between 10/20% (see DPDK Intel Vhost/virtio perf. reports)
|
||||
and 75% (using Napatech SmartNICs).
|
||||
|
||||
Packed Ring can be requested via image property or flavor extra spec.
|
||||
hw_virtio_packed_ring=true|false (default false)
|
||||
hw:virtio_packed_ring=true|false (default false)
|
||||
|
||||
Useful references:
|
||||
https://libvirt.org/formatdomain.html#virtio-related-options
|
||||
https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html
|
||||
https://specs.openstack.org/openstack/nova-specs/specs/2023.2/approved/virtio_packedring_configuration_support.html
|
||||
|
@ -52,7 +52,7 @@ psutil>=3.2.2 # BSD
|
||||
oslo.versionedobjects>=1.35.0 # Apache-2.0
|
||||
os-brick>=5.2 # Apache-2.0
|
||||
os-resource-classes>=1.1.0 # Apache-2.0
|
||||
os-traits>=2.10.0 # Apache-2.0
|
||||
os-traits>=3.0.0 # Apache-2.0
|
||||
os-vif>=3.1.0 # Apache-2.0
|
||||
castellan>=0.16.0 # Apache-2.0
|
||||
microversion-parse>=0.2.1 # Apache-2.0
|
||||
|
Loading…
Reference in New Issue
Block a user