Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions nova/tests/functional/regressions/test_bug_1951656.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from oslo_utils import uuidutils


from nova.tests.fixtures import libvirt as fakelibvirt
from nova.tests.functional.libvirt import test_vgpu
from nova.virt.libvirt import utils as libvirt_utils


class VGPUTestsLibvirt7_7(test_vgpu.VGPUTestBase):

def _create_mdev(self, physical_device, mdev_type, uuid=None):
# We need to fake the newly created sysfs object by adding a new
# FakeMdevDevice in the existing persisted Connection object so
# when asking to get the existing mdevs, we would see it.
if not uuid:
uuid = uuidutils.generate_uuid()
mdev_name = libvirt_utils.mdev_uuid2name(uuid)
libvirt_parent = self.pci2libvirt_address(physical_device)

# Libvirt 7.7 now creates mdevs with a parent_addr suffix.
new_mdev_name = '_'.join([mdev_name, libvirt_parent])

# Here, we get the right compute thanks by the self.current_host that
# was modified just before
connection = self.computes[
self._current_host].driver._host.get_connection()
connection.mdev_info.devices.update(
{mdev_name: fakelibvirt.FakeMdevDevice(dev_name=new_mdev_name,
type_id=mdev_type,
parent=libvirt_parent)})
return uuid

def setUp(self):
super(VGPUTestsLibvirt7_7, self).setUp()
extra_spec = {"resources:VGPU": "1"}
self.flavor = self._create_flavor(extra_spec=extra_spec)

# Start compute1 supporting only nvidia-11
self.flags(
enabled_mdev_types=fakelibvirt.NVIDIA_11_VGPU_TYPE,
group='devices')

self.compute1 = self.start_compute_with_vgpu('host1')

def test_create_servers_with_vgpu(self):

# Create a single instance against a specific compute node.
self._create_server(
image_uuid='155d900f-4e14-4e4c-a73d-069cbf4541e6',
flavor_id=self.flavor, host=self.compute1.host,
networks='auto', expected_state='ACTIVE')

self.assert_mdev_usage(self.compute1, expected_amount=1)

self._create_server(
image_uuid='155d900f-4e14-4e4c-a73d-069cbf4541e6',
flavor_id=self.flavor, host=self.compute1.host,
networks='auto', expected_state='ACTIVE')

self.assert_mdev_usage(self.compute1, expected_amount=2)
26 changes: 26 additions & 0 deletions nova/tests/unit/virt/libvirt/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3135,6 +3135,32 @@ def test_config_mdev_device(self):
config.LibvirtConfigNodeDeviceMdevInformation)
self.assertEqual("nvidia-11", obj.mdev_information.type)
self.assertEqual(12, obj.mdev_information.iommu_group)
self.assertIsNone(obj.mdev_information.uuid)

def test_config_mdev_device_uuid(self):
xmlin = """
<device>
<name>mdev_b2107403_110c_45b0_af87_32cc91597b8a_0000_41_00_0</name>
<path>/sys/devices/pci0000:40/0000:40:03.1/0000:41:00.0/b2107403-110c-45b0-af87-32cc91597b8a</path>
<parent>pci_0000_41_00_0</parent>
<driver>
<name>vfio_mdev</name>
</driver>
<capability type='mdev'>
<type id='nvidia-442'/>
<uuid>b2107403-110c-45b0-af87-32cc91597b8a</uuid>
<iommuGroup number='57'/>
</capability>
</device>"""

obj = config.LibvirtConfigNodeDevice()
obj.parse_str(xmlin)
self.assertIsInstance(obj.mdev_information,
config.LibvirtConfigNodeDeviceMdevInformation)
self.assertEqual("nvidia-442", obj.mdev_information.type)
self.assertEqual(57, obj.mdev_information.iommu_group)
self.assertEqual("b2107403-110c-45b0-af87-32cc91597b8a",
obj.mdev_information.uuid)

def test_config_vdpa_device(self):
xmlin = """
Expand Down
3 changes: 3 additions & 0 deletions nova/virt/libvirt/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3299,6 +3299,7 @@ def __init__(self, **kwargs):
root_name="capability", **kwargs)
self.type = None
self.iommu_group = None
self.uuid = None

def parse_dom(self, xmldoc):
super(LibvirtConfigNodeDeviceMdevInformation,
Expand All @@ -3308,6 +3309,8 @@ def parse_dom(self, xmldoc):
self.type = c.get('id')
if c.tag == "iommuGroup":
self.iommu_group = int(c.get('number'))
if c.tag == "uuid":
self.uuid = c.text


class LibvirtConfigNodeDeviceVpdCap(LibvirtConfigObject):
Expand Down
43 changes: 40 additions & 3 deletions nova/virt/libvirt/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -8019,15 +8019,52 @@ def _get_mdev_capable_devices(self, types=None):

def _get_mediated_device_information(self, devname):
"""Returns a dict of a mediated device."""
virtdev = self._host.device_lookup_by_name(devname)
# LP #1951656 - In Libvirt 7.7, the mdev name now includes the PCI
# address of the parent device (e.g. mdev_<uuid>_<pci_address>) due to
# the mdevctl allowing for multiple mediated devs having the same UUID
# defined (only one can be active at a time). Since the guest
# information doesn't have the parent ID, try to lookup which
# mediated device is available that matches the UUID. If multiple
# devices are found that match the UUID, then this is an error
# condition.
try:
virtdev = self._host.device_lookup_by_name(devname)
except libvirt.libvirtError as ex:
if ex.get_error_code() != libvirt.VIR_ERR_NO_NODE_DEVICE:
raise
mdevs = [dev for dev in self._host.list_mediated_devices()
if dev.startswith(devname)]
# If no matching devices are found, simply raise the original
# exception indicating that no devices are found.
if not mdevs:
raise
elif len(mdevs) > 1:
msg = ("The mediated device name %(devname)s refers to a UUID "
"that is present in multiple libvirt mediated devices. "
"Matching libvirt mediated devices are %(devices)s. "
"Mediated device UUIDs must be unique for Nova." %
{'devname': devname,
'devices': ', '.join(mdevs)})
raise exception.InvalidLibvirtMdevConfig(reason=msg)

LOG.debug('Found requested device %s as %s. Using that.',
devname, mdevs[0])
virtdev = self._host.device_lookup_by_name(mdevs[0])
xmlstr = virtdev.XMLDesc(0)
cfgdev = vconfig.LibvirtConfigNodeDevice()
cfgdev.parse_str(xmlstr)
# Starting with Libvirt 7.3, the uuid information is available in the
# node device information. If its there, use that. Otherwise,
# fall back to the previous behavior of parsing the uuid from the
# devname.
if cfgdev.mdev_information.uuid:
mdev_uuid = cfgdev.mdev_information.uuid
else:
mdev_uuid = libvirt_utils.mdev_name2uuid(cfgdev.name)

device = {
"dev_id": cfgdev.name,
# name is like mdev_00ead764_fdc0_46b6_8db9_2963f5c815b4
"uuid": libvirt_utils.mdev_name2uuid(cfgdev.name),
"uuid": mdev_uuid,
# the physical GPU PCI device
"parent": cfgdev.parent,
"type": cfgdev.mdev_information.type,
Expand Down
2 changes: 1 addition & 1 deletion nova/virt/libvirt/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -1503,7 +1503,7 @@ def list_mdev_capable_devices(self, flags=0):
def list_mediated_devices(self, flags=0):
"""Lookup mediated devices.
:returns: a list of virNodeDevice instance
:returns: a list of strings with the name of the instance
"""
return self._list_devices("mdev", flags=flags)

Expand Down
28 changes: 21 additions & 7 deletions nova/virt/libvirt/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,17 +581,31 @@ def get_default_machine_type(arch: str) -> ty.Optional[str]:


def mdev_name2uuid(mdev_name: str) -> str:
"""Convert an mdev name (of the form mdev_<uuid_with_underscores>) to a
uuid (of the form 8-4-4-4-12).
"""Convert an mdev name (of the form mdev_<uuid_with_underscores> or
mdev_<uuid_with_underscores>_<pciaddress>) to a uuid
(of the form 8-4-4-4-12).
:param mdev_name: the name of the mdev to parse the UUID from
:returns: string containing the uuid
"""
return str(uuid.UUID(mdev_name[5:].replace('_', '-')))
mdev_uuid = mdev_name[5:].replace('_', '-')
# Unconditionnally remove the PCI address from the name
mdev_uuid = mdev_uuid[:36]
return str(uuid.UUID(mdev_uuid))


def mdev_uuid2name(mdev_uuid: str, parent: str = None) -> str:
"""Convert an mdev uuid (of the form 8-4-4-4-12) and optionally its parent
device to a name (of the form mdev_<uuid_with_underscores>[_<pciid>]).
def mdev_uuid2name(mdev_uuid: str) -> str:
"""Convert an mdev uuid (of the form 8-4-4-4-12) to a name (of the form
mdev_<uuid_with_underscores>).
:param mdev_uuid: the uuid of the mediated device
:param parent: the parent device id for the mediated device
:returns: name of the mdev to reference in libvirt
"""
return "mdev_" + mdev_uuid.replace('-', '_')
name = "mdev_" + mdev_uuid.replace('-', '_')
if parent and parent.startswith('pci_'):
name = name + parent[4:]
return name


def get_flags_by_flavor_specs(flavor: 'objects.Flavor') -> ty.Set[str]:
Expand Down