Lookup nic feature by PCI address
In some environments the libvirt nodedev list can become out of sync with the current MAC address assigned to a netdev, As a result the nodedev lookup can fail. This results in an uncaught libvirt exception which breaks the update_available_resource function resultingin an incorrect resource view in the database. e.g. libvirt.libvirtError: Node device not found: no node device with matching name 'net_enp7s0f3v1_ea_60_77_1f_21_50' This change removes the dependency on the nodedev name when looking up nic feature flags. Change-Id: Ibf8dca4bd57b3bddb39955b53cc03564506f5754 Closes-Bug: #1883671
This commit is contained in:
parent
8ecc29bfcc
commit
efc27ff84c
|
@ -3,3 +3,4 @@ nova/virt/driver.py
|
||||||
nova/virt/hardware.py
|
nova/virt/hardware.py
|
||||||
nova/virt/libvirt/__init__.py
|
nova/virt/libvirt/__init__.py
|
||||||
nova/virt/libvirt/driver.py
|
nova/virt/libvirt/driver.py
|
||||||
|
nova/virt/libvirt/host.py
|
||||||
|
|
|
@ -200,27 +200,3 @@ def get_vf_num_by_pci_address(pci_addr):
|
||||||
if vf_num is None:
|
if vf_num is None:
|
||||||
raise exception.PciDeviceNotFoundById(id=pci_addr)
|
raise exception.PciDeviceNotFoundById(id=pci_addr)
|
||||||
return vf_num
|
return vf_num
|
||||||
|
|
||||||
|
|
||||||
def get_net_name_by_vf_pci_address(vfaddress):
|
|
||||||
"""Given the VF PCI address, returns the net device name.
|
|
||||||
|
|
||||||
Every VF is associated to a PCI network device. This function
|
|
||||||
returns the libvirt name given to this network device; e.g.:
|
|
||||||
|
|
||||||
<device>
|
|
||||||
<name>net_enp8s0f0_90_e2_ba_5e_a6_40</name>
|
|
||||||
...
|
|
||||||
|
|
||||||
In the libvirt parser information tree, the network device stores the
|
|
||||||
network capabilities associated to this device.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
mac = get_mac_by_pci_address(vfaddress).split(':')
|
|
||||||
ifname = get_ifname_by_pci_address(vfaddress)
|
|
||||||
return ("net_%(ifname)s_%(mac)s" %
|
|
||||||
{'ifname': ifname, 'mac': '_'.join(mac)})
|
|
||||||
except Exception:
|
|
||||||
LOG.warning("No net device was found for VF %(vfaddress)s",
|
|
||||||
{'vfaddress': vfaddress})
|
|
||||||
return
|
|
||||||
|
|
|
@ -266,46 +266,3 @@ class GetVfNumByPciAddressTestCase(test.NoDBTestCase):
|
||||||
utils.get_vf_num_by_pci_address,
|
utils.get_vf_num_by_pci_address,
|
||||||
self.pci_address
|
self.pci_address
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class GetNetNameByVfPciAddressTestCase(test.NoDBTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(GetNetNameByVfPciAddressTestCase, self).setUp()
|
|
||||||
self._get_mac = mock.patch.object(utils, 'get_mac_by_pci_address')
|
|
||||||
self.mock_get_mac = self._get_mac.start()
|
|
||||||
self._get_ifname = mock.patch.object(
|
|
||||||
utils, 'get_ifname_by_pci_address')
|
|
||||||
self.mock_get_ifname = self._get_ifname.start()
|
|
||||||
self.addCleanup(self._get_mac.stop)
|
|
||||||
self.addCleanup(self._get_ifname.stop)
|
|
||||||
|
|
||||||
self.mac = 'ca:fe:ca:fe:ca:fe'
|
|
||||||
self.if_name = 'enp7s0f0'
|
|
||||||
self.pci_address = '0000:07:02.1'
|
|
||||||
|
|
||||||
def test_correct_behaviour(self):
|
|
||||||
ref_net_name = 'net_enp7s0f0_ca_fe_ca_fe_ca_fe'
|
|
||||||
self.mock_get_mac.return_value = self.mac
|
|
||||||
self.mock_get_ifname.return_value = self.if_name
|
|
||||||
net_name = utils.get_net_name_by_vf_pci_address(self.pci_address)
|
|
||||||
self.assertEqual(ref_net_name, net_name)
|
|
||||||
self.mock_get_mac.assert_called_once_with(self.pci_address)
|
|
||||||
self.mock_get_ifname.assert_called_once_with(self.pci_address)
|
|
||||||
|
|
||||||
def test_wrong_mac(self):
|
|
||||||
self.mock_get_mac.side_effect = (
|
|
||||||
exception.PciDeviceNotFoundById(self.pci_address))
|
|
||||||
net_name = utils.get_net_name_by_vf_pci_address(self.pci_address)
|
|
||||||
self.assertIsNone(net_name)
|
|
||||||
self.mock_get_mac.assert_called_once_with(self.pci_address)
|
|
||||||
self.mock_get_ifname.assert_not_called()
|
|
||||||
|
|
||||||
def test_wrong_ifname(self):
|
|
||||||
self.mock_get_mac.return_value = self.mac
|
|
||||||
self.mock_get_ifname.side_effect = (
|
|
||||||
exception.PciDeviceNotFoundById(self.pci_address))
|
|
||||||
net_name = utils.get_net_name_by_vf_pci_address(self.pci_address)
|
|
||||||
self.assertIsNone(net_name)
|
|
||||||
self.mock_get_mac.assert_called_once_with(self.pci_address)
|
|
||||||
self.mock_get_ifname.assert_called_once_with(self.pci_address)
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
import time
|
import time
|
||||||
|
import typing as ty
|
||||||
|
|
||||||
import fixtures
|
import fixtures
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
@ -167,6 +168,10 @@ VIR_DOMAIN_BLOCK_COMMIT_RELATIVE = 4
|
||||||
VIR_CONNECT_LIST_DOMAINS_ACTIVE = 1
|
VIR_CONNECT_LIST_DOMAINS_ACTIVE = 1
|
||||||
VIR_CONNECT_LIST_DOMAINS_INACTIVE = 2
|
VIR_CONNECT_LIST_DOMAINS_INACTIVE = 2
|
||||||
|
|
||||||
|
# virConnectListAllNodeDevices flags
|
||||||
|
VIR_CONNECT_LIST_NODE_DEVICES_CAP_PCI_DEV = 2
|
||||||
|
VIR_CONNECT_LIST_NODE_DEVICES_CAP_NET = 16
|
||||||
|
|
||||||
# secret type
|
# secret type
|
||||||
VIR_SECRET_USAGE_TYPE_NONE = 0
|
VIR_SECRET_USAGE_TYPE_NONE = 0
|
||||||
VIR_SECRET_USAGE_TYPE_VOLUME = 1
|
VIR_SECRET_USAGE_TYPE_VOLUME = 1
|
||||||
|
@ -220,11 +225,12 @@ class FakePCIDevice(object):
|
||||||
- X540 Ethernet Controller Virtual Function (8086:1515)
|
- X540 Ethernet Controller Virtual Function (8086:1515)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
pci_default_parent = "pci_0000_80_01_0"
|
||||||
pci_device_template = textwrap.dedent("""
|
pci_device_template = textwrap.dedent("""
|
||||||
<device>
|
<device>
|
||||||
<name>pci_0000_81_%(slot)02x_%(function)d</name>
|
<name>pci_0000_81_%(slot)02x_%(function)d</name>
|
||||||
<path>/sys/devices/pci0000:80/0000:80:01.0/0000:81:%(slot)02x.%(function)d</path>
|
<path>/sys/devices/pci0000:80/0000:80:01.0/0000:81:%(slot)02x.%(function)d</path>
|
||||||
<parent>pci_0000_80_01_0</parent>
|
<parent>%(parent)s</parent>
|
||||||
<driver>
|
<driver>
|
||||||
<name>%(driver)s</name>
|
<name>%(driver)s</name>
|
||||||
</driver>
|
</driver>
|
||||||
|
@ -257,7 +263,7 @@ class FakePCIDevice(object):
|
||||||
is_capable_of_mdevs = False
|
is_capable_of_mdevs = False
|
||||||
|
|
||||||
def __init__(self, dev_type, slot, function, iommu_group, numa_node,
|
def __init__(self, dev_type, slot, function, iommu_group, numa_node,
|
||||||
vf_ratio=None, multiple_gpu_types=False):
|
vf_ratio=None, multiple_gpu_types=False, parent=None):
|
||||||
"""Populate pci devices
|
"""Populate pci devices
|
||||||
|
|
||||||
:param dev_type: (string) Indicates the type of the device (PCI, PF,
|
:param dev_type: (string) Indicates the type of the device (PCI, PF,
|
||||||
|
@ -347,6 +353,7 @@ class FakePCIDevice(object):
|
||||||
'capability': capability,
|
'capability': capability,
|
||||||
'iommu_group': iommu_group,
|
'iommu_group': iommu_group,
|
||||||
'numa_node': numa_node,
|
'numa_node': numa_node,
|
||||||
|
'parent': parent or self.pci_default_parent
|
||||||
}
|
}
|
||||||
# -1 is the sentinel set in /sys/bus/pci/devices/*/numa_node
|
# -1 is the sentinel set in /sys/bus/pci/devices/*/numa_node
|
||||||
# for no NUMA affinity. When the numa_node is set to -1 on a device
|
# for no NUMA affinity. When the numa_node is set to -1 on a device
|
||||||
|
@ -449,7 +456,7 @@ class HostPCIDevicesInfo(object):
|
||||||
iommu_group=iommu_group,
|
iommu_group=iommu_group,
|
||||||
numa_node=numa_node_pf,
|
numa_node=numa_node_pf,
|
||||||
vf_ratio=vf_ratio)
|
vf_ratio=vf_ratio)
|
||||||
|
pf_dev_name = pci_dev_name
|
||||||
# Generate VFs
|
# Generate VFs
|
||||||
for _ in range(vf_ratio):
|
for _ in range(vf_ratio):
|
||||||
function += 1
|
function += 1
|
||||||
|
@ -471,7 +478,8 @@ class HostPCIDevicesInfo(object):
|
||||||
function=function,
|
function=function,
|
||||||
iommu_group=iommu_group,
|
iommu_group=iommu_group,
|
||||||
numa_node=numa_node_pf,
|
numa_node=numa_node_pf,
|
||||||
vf_ratio=vf_ratio)
|
vf_ratio=vf_ratio,
|
||||||
|
parent=pf_dev_name)
|
||||||
|
|
||||||
slot += 1
|
slot += 1
|
||||||
|
|
||||||
|
@ -799,7 +807,8 @@ class NodeDevice(object):
|
||||||
def _parse_xml(self, xml):
|
def _parse_xml(self, xml):
|
||||||
tree = etree.fromstring(xml)
|
tree = etree.fromstring(xml)
|
||||||
root = tree.find('.')
|
root = tree.find('.')
|
||||||
self._name = root.get('name')
|
self._name = root.find('name').text
|
||||||
|
self._parent = root.find('parent').text
|
||||||
|
|
||||||
def attach(self):
|
def attach(self):
|
||||||
pass
|
pass
|
||||||
|
@ -810,6 +819,18 @@ class NodeDevice(object):
|
||||||
def reset(self):
|
def reset(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def XMLDesc(self, flags: int) -> str:
|
||||||
|
return self._xml
|
||||||
|
|
||||||
|
def parent(self) -> str:
|
||||||
|
return self._parent
|
||||||
|
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
def listCaps(self) -> ty.List[str]:
|
||||||
|
return [self.name().split('_')[0]]
|
||||||
|
|
||||||
|
|
||||||
class Domain(object):
|
class Domain(object):
|
||||||
def __init__(self, connection, xml, running=False, transient=False):
|
def __init__(self, connection, xml, running=False, transient=False):
|
||||||
|
@ -1711,6 +1732,13 @@ class Connection(object):
|
||||||
def secretDefineXML(self, xml):
|
def secretDefineXML(self, xml):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def listAllDevices(self, flags):
|
||||||
|
# Note this is incomplete as we do not filter
|
||||||
|
# based on the flags however it is enough for our
|
||||||
|
# current testing.
|
||||||
|
return [NodeDevice(self, xml=dev.XMLDesc(0))
|
||||||
|
for dev in self.pci_info.devices.values()]
|
||||||
|
|
||||||
|
|
||||||
def openAuth(uri, auth, flags=0):
|
def openAuth(uri, auth, flags=0):
|
||||||
|
|
||||||
|
|
|
@ -128,8 +128,8 @@ CONF = nova.conf.CONF
|
||||||
|
|
||||||
_fake_network_info = fake_network.fake_get_instance_nw_info
|
_fake_network_info = fake_network.fake_get_instance_nw_info
|
||||||
|
|
||||||
_fake_NodeDevXml = \
|
_fake_NodeDevXml = {
|
||||||
{"pci_0000_04_00_3": """
|
"pci_0000_04_00_3": """
|
||||||
<device>
|
<device>
|
||||||
<name>pci_0000_04_00_3</name>
|
<name>pci_0000_04_00_3</name>
|
||||||
<parent>pci_0000_00_01_1</parent>
|
<parent>pci_0000_00_01_1</parent>
|
||||||
|
@ -154,7 +154,7 @@ _fake_NodeDevXml = \
|
||||||
"pci_0000_04_10_7": """
|
"pci_0000_04_10_7": """
|
||||||
<device>
|
<device>
|
||||||
<name>pci_0000_04_10_7</name>
|
<name>pci_0000_04_10_7</name>
|
||||||
<parent>pci_0000_00_01_1</parent>
|
<parent>pci_0000_04_00_3</parent>
|
||||||
<driver>
|
<driver>
|
||||||
<name>igbvf</name>
|
<name>igbvf</name>
|
||||||
</driver>
|
</driver>
|
||||||
|
@ -176,7 +176,7 @@ _fake_NodeDevXml = \
|
||||||
"pci_0000_04_11_7": """
|
"pci_0000_04_11_7": """
|
||||||
<device>
|
<device>
|
||||||
<name>pci_0000_04_11_7</name>
|
<name>pci_0000_04_11_7</name>
|
||||||
<parent>pci_0000_00_01_1</parent>
|
<parent>pci_0000_04_00_3</parent>
|
||||||
<driver>
|
<driver>
|
||||||
<name>igbvf</name>
|
<name>igbvf</name>
|
||||||
</driver>
|
</driver>
|
||||||
|
@ -281,6 +281,26 @@ _fake_NodeDevXml = \
|
||||||
<link validity='sta' speed='8' width='16'/>
|
<link validity='sta' speed='8' width='16'/>
|
||||||
</pci-express>
|
</pci-express>
|
||||||
</capability>
|
</capability>
|
||||||
|
</device>""",
|
||||||
|
"net_enp2s1_02_9a_a1_37_be_54": """
|
||||||
|
<device>
|
||||||
|
<name>net_enp2s1_02_9a_a1_37_be_54</name>
|
||||||
|
<path>/sys/devices/pci0000:00/0000:04:00.3/0000:04:10.7/net/enp2s1</path>
|
||||||
|
<parent>pci_0000_04_10_7</parent>
|
||||||
|
<capability type='net'>
|
||||||
|
<interface>enp2s1</interface>
|
||||||
|
<address>02:9a:a1:37:be:54</address>
|
||||||
|
<link state='down'/>
|
||||||
|
<feature name='rx'/>
|
||||||
|
<feature name='tx'/>
|
||||||
|
<feature name='sg'/>
|
||||||
|
<feature name='tso'/>
|
||||||
|
<feature name='gso'/>
|
||||||
|
<feature name='gro'/>
|
||||||
|
<feature name='rxvlan'/>
|
||||||
|
<feature name='txvlan'/>
|
||||||
|
<capability type='80203'/>
|
||||||
|
</capability>
|
||||||
</device>""",
|
</device>""",
|
||||||
"net_enp2s2_02_9a_a1_37_be_54": """
|
"net_enp2s2_02_9a_a1_37_be_54": """
|
||||||
<device>
|
<device>
|
||||||
|
@ -343,6 +363,15 @@ _fake_NodeDevXml = \
|
||||||
""",
|
""",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_fake_NodeDevXml_parents = {
|
||||||
|
name: etree.fromstring(xml).find("parent").text
|
||||||
|
for name, xml in _fake_NodeDevXml.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
_fake_NodeDevXml_children = defaultdict(list)
|
||||||
|
for key, val in _fake_NodeDevXml_parents.items():
|
||||||
|
_fake_NodeDevXml_children[val].append(key)
|
||||||
|
|
||||||
_fake_cpu_info = {
|
_fake_cpu_info = {
|
||||||
"arch": "test_arch",
|
"arch": "test_arch",
|
||||||
"model": "test_model",
|
"model": "test_model",
|
||||||
|
@ -17225,70 +17254,55 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
|
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
|
||||||
dev_name = "net_enp2s2_02_9a_a1_37_be_54"
|
dev_name = "net_enp2s2_02_9a_a1_37_be_54"
|
||||||
parent_address = "pci_0000_04_11_7"
|
parent_address = "pci_0000_04_11_7"
|
||||||
node_dev = FakeNodeDevice(_fake_NodeDevXml[dev_name])
|
net_dev = fakelibvirt.NodeDevice(
|
||||||
|
drvr._get_connection(), xml=_fake_NodeDevXml[dev_name])
|
||||||
with mock.patch.object(pci_utils, 'get_net_name_by_vf_pci_address',
|
pci_dev = fakelibvirt.NodeDevice(
|
||||||
return_value=dev_name) as mock_get_net_name, \
|
drvr._get_connection(), xml=_fake_NodeDevXml[parent_address])
|
||||||
mock.patch.object(drvr._host, 'device_lookup_by_name',
|
actualvf = drvr._get_pcinet_info(pci_dev, [net_dev])
|
||||||
return_value=node_dev) as mock_dev_lookup:
|
expect_vf = ["rx", "tx", "sg", "tso", "gso", "gro", "rxvlan", "txvlan"]
|
||||||
actualvf = drvr._get_pcinet_info(parent_address)
|
self.assertEqual(expect_vf, actualvf)
|
||||||
expect_vf = {
|
|
||||||
"name": dev_name,
|
|
||||||
"capabilities": ["rx", "tx", "sg", "tso", "gso", "gro",
|
|
||||||
"rxvlan", "txvlan"]
|
|
||||||
}
|
|
||||||
self.assertEqual(expect_vf, actualvf)
|
|
||||||
mock_get_net_name.assert_called_once_with(parent_address)
|
|
||||||
mock_dev_lookup.assert_called_once_with(dev_name)
|
|
||||||
|
|
||||||
def test_get_pcinet_info_raises(self):
|
|
||||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
|
|
||||||
dev_name = "net_enp2s2_02_9a_a1_37_be_54"
|
|
||||||
parent_address = "pci_0000_04_11_7"
|
|
||||||
|
|
||||||
with mock.patch.object(pci_utils, 'get_net_name_by_vf_pci_address',
|
|
||||||
return_value=dev_name) as mock_get_net_name, \
|
|
||||||
mock.patch.object(
|
|
||||||
drvr._host, 'device_lookup_by_name',
|
|
||||||
side_effect=fakelibvirt.libvirtError("message")
|
|
||||||
) as mock_dev_lookup:
|
|
||||||
actualvf = drvr._get_pcinet_info(parent_address)
|
|
||||||
self.assertIsNone(actualvf)
|
|
||||||
mock_get_net_name.assert_called_once_with(parent_address)
|
|
||||||
mock_dev_lookup.assert_called_once_with(dev_name)
|
|
||||||
|
|
||||||
@mock.patch.object(pci_utils, 'get_ifname_by_pci_address')
|
@mock.patch.object(pci_utils, 'get_ifname_by_pci_address')
|
||||||
def test_get_pcidev_info_non_nic(self, mock_get_ifname):
|
def test_get_pcidev_info_non_nic(self, mock_get_ifname):
|
||||||
self.stub_out('nova.virt.libvirt.host.Host.device_lookup_by_name',
|
|
||||||
lambda self, name: FakeNodeDevice(
|
|
||||||
_fake_NodeDevXml[name]))
|
|
||||||
|
|
||||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
|
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
|
||||||
id = "pci_0000_04_10_7"
|
dev_name = "pci_0000_04_11_7"
|
||||||
mock_get_ifname.side_effect = exception.PciDeviceNotFoundById(id=id)
|
pci_dev = fakelibvirt.NodeDevice(
|
||||||
actualvf = drvr._get_pcidev_info(id)
|
drvr._get_connection(), xml=_fake_NodeDevXml[dev_name])
|
||||||
|
actual_vf = drvr._get_pcidev_info(dev_name, pci_dev, [])
|
||||||
expect_vf = {
|
expect_vf = {
|
||||||
"dev_id": id,
|
"dev_id": dev_name, "address": "0000:04:11.7",
|
||||||
"address": "0000:04:10.7",
|
"product_id": '1520', "numa_node": 0,
|
||||||
"product_id": '1520',
|
"vendor_id": '8086', "label": 'label_8086_1520',
|
||||||
"numa_node": None,
|
|
||||||
"vendor_id": '8086',
|
|
||||||
"label": 'label_8086_1520',
|
|
||||||
"dev_type": fields.PciDeviceType.SRIOV_VF,
|
"dev_type": fields.PciDeviceType.SRIOV_VF,
|
||||||
'parent_addr': '0000:04:00.3',
|
'parent_addr': '0000:04:00.3',
|
||||||
}
|
}
|
||||||
self.assertEqual(expect_vf, actualvf)
|
self.assertEqual(expect_vf, actual_vf)
|
||||||
|
mock_get_ifname.assert_not_called()
|
||||||
|
|
||||||
@mock.patch.object(pci_utils, 'get_ifname_by_pci_address',
|
@mock.patch.object(pci_utils, 'get_ifname_by_pci_address',
|
||||||
return_value='ens1')
|
return_value='ens1')
|
||||||
def test_get_pcidev_info(self, mock_get_ifname):
|
def test_get_pcidev_info(self, mock_get_ifname):
|
||||||
self.stub_out('nova.virt.libvirt.host.Host.device_lookup_by_name',
|
|
||||||
lambda self, name: FakeNodeDevice(
|
|
||||||
_fake_NodeDevXml[name]))
|
|
||||||
|
|
||||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
|
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
|
||||||
|
|
||||||
actualvf = drvr._get_pcidev_info("pci_0000_04_00_3")
|
devs = {
|
||||||
|
"pci_0000_04_00_3", "pci_0000_04_10_7", "pci_0000_04_11_7",
|
||||||
|
"pci_0000_04_00_1", "pci_0000_03_00_0", "pci_0000_03_00_1"
|
||||||
|
}
|
||||||
|
node_devs = {}
|
||||||
|
for dev_name in devs:
|
||||||
|
node_devs[dev_name] = (
|
||||||
|
fakelibvirt.NodeDevice(
|
||||||
|
drvr._get_connection(), xml=_fake_NodeDevXml[dev_name]))
|
||||||
|
for child in _fake_NodeDevXml_children[dev_name]:
|
||||||
|
node_devs[child] = (
|
||||||
|
fakelibvirt.NodeDevice(
|
||||||
|
drvr._get_connection(),
|
||||||
|
xml=_fake_NodeDevXml[child]))
|
||||||
|
net_devs = [
|
||||||
|
dev for dev in node_devs.values() if dev.name() not in devs]
|
||||||
|
|
||||||
|
name = "pci_0000_04_00_3"
|
||||||
|
actual_vf = drvr._get_pcidev_info(name, node_devs[name], net_devs)
|
||||||
expect_vf = {
|
expect_vf = {
|
||||||
"dev_id": "pci_0000_04_00_3",
|
"dev_id": "pci_0000_04_00_3",
|
||||||
"address": "0000:04:00.3",
|
"address": "0000:04:00.3",
|
||||||
|
@ -17298,9 +17312,10 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||||
"label": 'label_8086_1521',
|
"label": 'label_8086_1521',
|
||||||
"dev_type": fields.PciDeviceType.SRIOV_PF,
|
"dev_type": fields.PciDeviceType.SRIOV_PF,
|
||||||
}
|
}
|
||||||
self.assertEqual(expect_vf, actualvf)
|
self.assertEqual(expect_vf, actual_vf)
|
||||||
|
|
||||||
actualvf = drvr._get_pcidev_info("pci_0000_04_10_7")
|
name = "pci_0000_04_10_7"
|
||||||
|
actual_vf = drvr._get_pcidev_info(name, node_devs[name], net_devs)
|
||||||
expect_vf = {
|
expect_vf = {
|
||||||
"dev_id": "pci_0000_04_10_7",
|
"dev_id": "pci_0000_04_10_7",
|
||||||
"address": "0000:04:10.7",
|
"address": "0000:04:10.7",
|
||||||
|
@ -17311,29 +17326,32 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||||
"dev_type": fields.PciDeviceType.SRIOV_VF,
|
"dev_type": fields.PciDeviceType.SRIOV_VF,
|
||||||
"parent_addr": '0000:04:00.3',
|
"parent_addr": '0000:04:00.3',
|
||||||
"parent_ifname": "ens1",
|
"parent_ifname": "ens1",
|
||||||
|
"capabilities": {
|
||||||
|
"network": ["rx", "tx", "sg", "tso", "gso", "gro",
|
||||||
|
"rxvlan", "txvlan"]},
|
||||||
}
|
}
|
||||||
self.assertEqual(expect_vf, actualvf)
|
self.assertEqual(expect_vf, actual_vf)
|
||||||
|
|
||||||
with mock.patch.object(pci_utils, 'get_net_name_by_vf_pci_address',
|
name = "pci_0000_04_11_7"
|
||||||
return_value="net_enp2s2_02_9a_a1_37_be_54"):
|
actual_vf = drvr._get_pcidev_info(name, node_devs[name], net_devs)
|
||||||
actualvf = drvr._get_pcidev_info("pci_0000_04_11_7")
|
expect_vf = {
|
||||||
expect_vf = {
|
"dev_id": "pci_0000_04_11_7",
|
||||||
"dev_id": "pci_0000_04_11_7",
|
"address": "0000:04:11.7",
|
||||||
"address": "0000:04:11.7",
|
"product_id": '1520',
|
||||||
"product_id": '1520',
|
"vendor_id": '8086',
|
||||||
"vendor_id": '8086',
|
"numa_node": 0,
|
||||||
"numa_node": 0,
|
"label": 'label_8086_1520',
|
||||||
"label": 'label_8086_1520',
|
"dev_type": fields.PciDeviceType.SRIOV_VF,
|
||||||
"dev_type": fields.PciDeviceType.SRIOV_VF,
|
"parent_addr": '0000:04:00.3',
|
||||||
"parent_addr": '0000:04:00.3',
|
"capabilities": {
|
||||||
"capabilities": {
|
"network": ["rx", "tx", "sg", "tso", "gso", "gro",
|
||||||
"network": ["rx", "tx", "sg", "tso", "gso", "gro",
|
"rxvlan", "txvlan"]},
|
||||||
"rxvlan", "txvlan"]},
|
"parent_ifname": "ens1",
|
||||||
"parent_ifname": "ens1",
|
}
|
||||||
}
|
self.assertEqual(expect_vf, actual_vf)
|
||||||
self.assertEqual(expect_vf, actualvf)
|
|
||||||
|
|
||||||
actualvf = drvr._get_pcidev_info("pci_0000_04_00_1")
|
name = "pci_0000_04_00_1"
|
||||||
|
actual_vf = drvr._get_pcidev_info(name, node_devs[name], net_devs)
|
||||||
expect_vf = {
|
expect_vf = {
|
||||||
"dev_id": "pci_0000_04_00_1",
|
"dev_id": "pci_0000_04_00_1",
|
||||||
"address": "0000:04:00.1",
|
"address": "0000:04:00.1",
|
||||||
|
@ -17343,9 +17361,10 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||||
"label": 'label_15b3_1013',
|
"label": 'label_15b3_1013',
|
||||||
"dev_type": fields.PciDeviceType.STANDARD,
|
"dev_type": fields.PciDeviceType.STANDARD,
|
||||||
}
|
}
|
||||||
self.assertEqual(expect_vf, actualvf)
|
self.assertEqual(expect_vf, actual_vf)
|
||||||
|
|
||||||
actualvf = drvr._get_pcidev_info("pci_0000_03_00_0")
|
name = "pci_0000_03_00_0"
|
||||||
|
actual_vf = drvr._get_pcidev_info(name, node_devs[name], net_devs)
|
||||||
expect_vf = {
|
expect_vf = {
|
||||||
"dev_id": "pci_0000_03_00_0",
|
"dev_id": "pci_0000_03_00_0",
|
||||||
"address": "0000:03:00.0",
|
"address": "0000:03:00.0",
|
||||||
|
@ -17355,9 +17374,10 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||||
"label": 'label_15b3_1013',
|
"label": 'label_15b3_1013',
|
||||||
"dev_type": fields.PciDeviceType.SRIOV_PF,
|
"dev_type": fields.PciDeviceType.SRIOV_PF,
|
||||||
}
|
}
|
||||||
self.assertEqual(expect_vf, actualvf)
|
self.assertEqual(expect_vf, actual_vf)
|
||||||
|
|
||||||
actualvf = drvr._get_pcidev_info("pci_0000_03_00_1")
|
name = "pci_0000_03_00_1"
|
||||||
|
actual_vf = drvr._get_pcidev_info(name, node_devs[name], net_devs)
|
||||||
expect_vf = {
|
expect_vf = {
|
||||||
"dev_id": "pci_0000_03_00_1",
|
"dev_id": "pci_0000_03_00_1",
|
||||||
"address": "0000:03:00.1",
|
"address": "0000:03:00.1",
|
||||||
|
@ -17367,36 +17387,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||||
"label": 'label_15b3_1013',
|
"label": 'label_15b3_1013',
|
||||||
"dev_type": fields.PciDeviceType.SRIOV_PF,
|
"dev_type": fields.PciDeviceType.SRIOV_PF,
|
||||||
}
|
}
|
||||||
self.assertEqual(expect_vf, actualvf)
|
self.assertEqual(expect_vf, actual_vf)
|
||||||
|
|
||||||
def test_list_devices_not_supported(self):
|
|
||||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
|
|
||||||
|
|
||||||
# Handle just the NO_SUPPORT error
|
|
||||||
not_supported_exc = fakelibvirt.make_libvirtError(
|
|
||||||
fakelibvirt.libvirtError,
|
|
||||||
'this function is not supported by the connection driver:'
|
|
||||||
' virNodeNumOfDevices',
|
|
||||||
error_code=fakelibvirt.VIR_ERR_NO_SUPPORT)
|
|
||||||
|
|
||||||
with mock.patch.object(host.Host, '_list_devices',
|
|
||||||
side_effect=not_supported_exc):
|
|
||||||
self.assertEqual('[]', drvr._get_pci_passthrough_devices())
|
|
||||||
|
|
||||||
# We cache not supported status to avoid emitting too many logging
|
|
||||||
# messages. Clear this value to test the other exception case.
|
|
||||||
del drvr._list_devices_supported
|
|
||||||
|
|
||||||
# Other errors should not be caught
|
|
||||||
other_exc = fakelibvirt.make_libvirtError(
|
|
||||||
fakelibvirt.libvirtError,
|
|
||||||
'other exc',
|
|
||||||
error_code=fakelibvirt.VIR_ERR_NO_DOMAIN)
|
|
||||||
|
|
||||||
with mock.patch.object(host.Host, '_list_devices',
|
|
||||||
side_effect=other_exc):
|
|
||||||
self.assertRaises(fakelibvirt.libvirtError,
|
|
||||||
drvr._get_pci_passthrough_devices)
|
|
||||||
|
|
||||||
@mock.patch.object(pci_utils, 'get_ifname_by_pci_address',
|
@mock.patch.object(pci_utils, 'get_ifname_by_pci_address',
|
||||||
return_value='ens1')
|
return_value='ens1')
|
||||||
|
@ -17404,13 +17395,31 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||||
return_value=['pci_0000_04_00_3', 'pci_0000_04_10_7',
|
return_value=['pci_0000_04_00_3', 'pci_0000_04_10_7',
|
||||||
'pci_0000_04_11_7'])
|
'pci_0000_04_11_7'])
|
||||||
def test_get_pci_passthrough_devices(self, mock_list, mock_get_ifname):
|
def test_get_pci_passthrough_devices(self, mock_list, mock_get_ifname):
|
||||||
self.stub_out('nova.virt.libvirt.host.Host.device_lookup_by_name',
|
|
||||||
lambda self, name: FakeNodeDevice(
|
|
||||||
_fake_NodeDevXml[name]))
|
|
||||||
|
|
||||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
|
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
|
||||||
actjson = drvr._get_pci_passthrough_devices()
|
|
||||||
|
|
||||||
|
devs = ['pci_0000_04_00_3', 'pci_0000_04_10_7', 'pci_0000_04_11_7']
|
||||||
|
node_devs = {}
|
||||||
|
for dev_name in devs:
|
||||||
|
node_devs[dev_name] = (
|
||||||
|
fakelibvirt.NodeDevice(
|
||||||
|
drvr._get_connection(), xml=_fake_NodeDevXml[dev_name]))
|
||||||
|
for child in _fake_NodeDevXml_children[dev_name]:
|
||||||
|
node_devs[child] = (
|
||||||
|
fakelibvirt.NodeDevice(
|
||||||
|
drvr._get_connection(),
|
||||||
|
xml=_fake_NodeDevXml[child]))
|
||||||
|
|
||||||
|
self.useFixture(fixtures.MockPatchObject(
|
||||||
|
drvr._host, 'list_all_devices',
|
||||||
|
return_value=node_devs.values()))
|
||||||
|
|
||||||
|
self.stub_out(
|
||||||
|
'nova.virt.libvirt.host.Host.device_lookup_by_name',
|
||||||
|
lambda self, name: node_devs.get(name))
|
||||||
|
|
||||||
|
actjson = drvr._get_pci_passthrough_devices()
|
||||||
|
mock_list.assert_not_called()
|
||||||
expectvfs = [
|
expectvfs = [
|
||||||
{
|
{
|
||||||
"dev_id": "pci_0000_04_00_3",
|
"dev_id": "pci_0000_04_00_3",
|
||||||
|
@ -17449,17 +17458,15 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||||
actualvfs = jsonutils.loads(actjson)
|
actualvfs = jsonutils.loads(actjson)
|
||||||
for dev in range(len(actualvfs)):
|
for dev in range(len(actualvfs)):
|
||||||
for key in actualvfs[dev].keys():
|
for key in actualvfs[dev].keys():
|
||||||
if key not in ['phys_function', 'virt_functions', 'label']:
|
if key not in ['phys_function', 'virt_functions',
|
||||||
|
'label', 'capabilities']:
|
||||||
self.assertEqual(expectvfs[dev][key], actualvfs[dev][key])
|
self.assertEqual(expectvfs[dev][key], actualvfs[dev][key])
|
||||||
mock_list.assert_called_once_with()
|
|
||||||
|
|
||||||
# The first call for every VF is to determine parent_ifname and
|
# The first call for every VF is to determine parent_ifname and
|
||||||
# the second call to determine the MAC address.
|
# the second call to determine the MAC address.
|
||||||
mock_get_ifname.assert_has_calls([
|
mock_get_ifname.assert_has_calls([
|
||||||
mock.call('0000:04:10.7', pf_interface=True),
|
mock.call('0000:04:10.7', pf_interface=True),
|
||||||
mock.call('0000:04:10.7', False),
|
|
||||||
mock.call('0000:04:11.7', pf_interface=True),
|
mock.call('0000:04:11.7', pf_interface=True),
|
||||||
mock.call('0000:04:11.7', False)
|
|
||||||
])
|
])
|
||||||
|
|
||||||
@mock.patch.object(host.Host, 'has_min_version',
|
@mock.patch.object(host.Host, 'has_min_version',
|
||||||
|
|
|
@ -457,7 +457,7 @@ class FakeLibvirtTests(test.NoDBTestCase):
|
||||||
vf_xml = """<device>
|
vf_xml = """<device>
|
||||||
<name>pci_0000_81_00_1</name>
|
<name>pci_0000_81_00_1</name>
|
||||||
<path>/sys/devices/pci0000:80/0000:80:01.0/0000:81:00.1</path>
|
<path>/sys/devices/pci0000:80/0000:80:01.0/0000:81:00.1</path>
|
||||||
<parent>pci_0000_80_01_0</parent>
|
<parent>pci_0000_81_00_0</parent>
|
||||||
<driver>
|
<driver>
|
||||||
<name>ixgbevf</name>
|
<name>ixgbevf</name>
|
||||||
</driver>
|
</driver>
|
||||||
|
|
|
@ -1136,6 +1136,74 @@ Active: 8381604 kB
|
||||||
self.host.list_mediated_devices(8)
|
self.host.list_mediated_devices(8)
|
||||||
mock_listDevices.assert_called_once_with('mdev', flags=8)
|
mock_listDevices.assert_called_once_with('mdev', flags=8)
|
||||||
|
|
||||||
|
def test_list_all_devices(self):
|
||||||
|
with mock.patch.object(
|
||||||
|
self.host.get_connection(),
|
||||||
|
"listAllDevices") as mock_list_all_devices:
|
||||||
|
xml_str = """
|
||||||
|
<device>
|
||||||
|
<name>pci_0000_04_00_3</name>
|
||||||
|
<parent>pci_0000_00_01_1</parent>
|
||||||
|
<driver>
|
||||||
|
<name>igb</name>
|
||||||
|
</driver>
|
||||||
|
<capability type='pci'>
|
||||||
|
<domain>0</domain>
|
||||||
|
<bus>4</bus>
|
||||||
|
<slot>0</slot>
|
||||||
|
<function>3</function>
|
||||||
|
<product id='0x1521'>I350 Gigabit Network Connection</product>
|
||||||
|
<vendor id='0x8086'>Intel Corporation</vendor>
|
||||||
|
<capability type='virt_functions'>
|
||||||
|
<address domain='0x0000' bus='0x04' slot='0x10' function='0x3'/>
|
||||||
|
<address domain='0x0000' bus='0x04' slot='0x10' function='0x7'/>
|
||||||
|
<address domain='0x0000' bus='0x04' slot='0x11' function='0x3'/>
|
||||||
|
<address domain='0x0000' bus='0x04' slot='0x11' function='0x7'/>
|
||||||
|
</capability>
|
||||||
|
</capability>
|
||||||
|
</device>"""
|
||||||
|
pci_dev = fakelibvirt.NodeDevice(None, xml=xml_str)
|
||||||
|
node_devs = [pci_dev]
|
||||||
|
mock_list_all_devices.return_value = node_devs
|
||||||
|
ret = self.host.list_all_devices(flags=42)
|
||||||
|
self.assertEqual(node_devs, ret)
|
||||||
|
mock_list_all_devices.assert_called_once_with(42)
|
||||||
|
|
||||||
|
def test_list_all_devices_raises(self):
|
||||||
|
with mock.patch.object(
|
||||||
|
self.host.get_connection(),
|
||||||
|
"listAllDevices") as mock_list_all_devices:
|
||||||
|
xml_str = """
|
||||||
|
<device>
|
||||||
|
<name>pci_0000_04_00_3</name>
|
||||||
|
<parent>pci_0000_00_01_1</parent>
|
||||||
|
<driver>
|
||||||
|
<name>igb</name>
|
||||||
|
</driver>
|
||||||
|
<capability type='pci'>
|
||||||
|
<domain>0</domain>
|
||||||
|
<bus>4</bus>
|
||||||
|
<slot>0</slot>
|
||||||
|
<function>3</function>
|
||||||
|
<product id='0x1521'>I350 Gigabit Network Connection</product>
|
||||||
|
<vendor id='0x8086'>Intel Corporation</vendor>
|
||||||
|
<capability type='virt_functions'>
|
||||||
|
<address domain='0x0000' bus='0x04' slot='0x10' function='0x3'/>
|
||||||
|
<address domain='0x0000' bus='0x04' slot='0x10' function='0x7'/>
|
||||||
|
<address domain='0x0000' bus='0x04' slot='0x11' function='0x3'/>
|
||||||
|
<address domain='0x0000' bus='0x04' slot='0x11' function='0x7'/>
|
||||||
|
</capability>
|
||||||
|
</capability>
|
||||||
|
</device>"""
|
||||||
|
pci_dev = fakelibvirt.NodeDevice(None, xml=xml_str)
|
||||||
|
node_devs = [pci_dev]
|
||||||
|
mock_list_all_devices.return_value = node_devs
|
||||||
|
mock_list_all_devices.side_effect = fakelibvirt.libvirtError(
|
||||||
|
"message")
|
||||||
|
ret = self.host.list_all_devices(flags=42)
|
||||||
|
self.assertEqual([], ret)
|
||||||
|
mock_list_all_devices.assert_called_once_with(42)
|
||||||
|
|
||||||
@mock.patch.object(fakelibvirt.virConnect, "listDevices")
|
@mock.patch.object(fakelibvirt.virConnect, "listDevices")
|
||||||
def test_list_devices(self, mock_listDevices):
|
def test_list_devices(self, mock_listDevices):
|
||||||
self.host._list_devices('mdev', 8)
|
self.host._list_devices('mdev', 8)
|
||||||
|
|
|
@ -6908,35 +6908,41 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||||
cpu_info['features'] = features
|
cpu_info['features'] = features
|
||||||
return cpu_info
|
return cpu_info
|
||||||
|
|
||||||
def _get_pcinet_info(self, vf_address):
|
def _get_pcinet_info(
|
||||||
|
self,
|
||||||
|
dev: 'libvirt.virNodeDevice',
|
||||||
|
net_devs: ty.List['libvirt.virNodeDevice']
|
||||||
|
) -> ty.Optional[ty.List[str]]:
|
||||||
"""Returns a dict of NET device."""
|
"""Returns a dict of NET device."""
|
||||||
devname = pci_utils.get_net_name_by_vf_pci_address(vf_address)
|
net_dev = {dev.parent(): dev for dev in net_devs}.get(dev.name(), None)
|
||||||
if not devname:
|
if net_dev is None:
|
||||||
return None
|
return None
|
||||||
|
xmlstr = net_dev.XMLDesc(0)
|
||||||
try:
|
|
||||||
virtdev = self._host.device_lookup_by_name(devname)
|
|
||||||
except libvirt.libvirtError as ex:
|
|
||||||
LOG.warning(ex)
|
|
||||||
return None
|
|
||||||
xmlstr = virtdev.XMLDesc(0)
|
|
||||||
cfgdev = vconfig.LibvirtConfigNodeDevice()
|
cfgdev = vconfig.LibvirtConfigNodeDevice()
|
||||||
cfgdev.parse_str(xmlstr)
|
cfgdev.parse_str(xmlstr)
|
||||||
return {'name': cfgdev.name,
|
return cfgdev.pci_capability.features
|
||||||
'capabilities': cfgdev.pci_capability.features}
|
|
||||||
|
|
||||||
def _get_pcidev_info(self, devname):
|
def _get_pcidev_info(
|
||||||
|
self,
|
||||||
|
devname: str,
|
||||||
|
dev: 'libvirt.virNodeDevice',
|
||||||
|
net_devs: ty.List['libvirt.virNodeDevice']
|
||||||
|
) -> ty.Dict[str, ty.Union[str, dict]]:
|
||||||
"""Returns a dict of PCI device."""
|
"""Returns a dict of PCI device."""
|
||||||
|
|
||||||
def _get_device_type(cfgdev, pci_address):
|
def _get_device_type(
|
||||||
|
cfgdev: vconfig.LibvirtConfigNodeDevice,
|
||||||
|
pci_address: str,
|
||||||
|
device: 'libvirt.virNodeDevice',
|
||||||
|
net_devs: ty.List['libvirt.virNodeDevice']
|
||||||
|
) -> ty.Dict[str, str]:
|
||||||
"""Get a PCI device's device type.
|
"""Get a PCI device's device type.
|
||||||
|
|
||||||
An assignable PCI device can be a normal PCI device,
|
An assignable PCI device can be a normal PCI device,
|
||||||
a SR-IOV Physical Function (PF), or a SR-IOV Virtual
|
a SR-IOV Physical Function (PF), or a SR-IOV Virtual
|
||||||
Function (VF). Only normal PCI devices or SR-IOV VFs
|
Function (VF).
|
||||||
are assignable, while SR-IOV PFs are always owned by
|
|
||||||
hypervisor.
|
|
||||||
"""
|
"""
|
||||||
|
net_dev_parents = {dev.parent() for dev in net_devs}
|
||||||
for fun_cap in cfgdev.pci_capability.fun_capability:
|
for fun_cap in cfgdev.pci_capability.fun_capability:
|
||||||
if fun_cap.type == 'virt_functions':
|
if fun_cap.type == 'virt_functions':
|
||||||
return {
|
return {
|
||||||
|
@ -6954,35 +6960,34 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||||
'parent_addr': phys_address,
|
'parent_addr': phys_address,
|
||||||
}
|
}
|
||||||
parent_ifname = None
|
parent_ifname = None
|
||||||
try:
|
# NOTE(sean-k-mooney): if the VF is a parent of a netdev
|
||||||
|
# the PF should also have a netdev.
|
||||||
|
if device.name() in net_dev_parents:
|
||||||
parent_ifname = pci_utils.get_ifname_by_pci_address(
|
parent_ifname = pci_utils.get_ifname_by_pci_address(
|
||||||
pci_address, pf_interface=True)
|
pci_address, pf_interface=True)
|
||||||
except exception.PciDeviceNotFoundById:
|
|
||||||
# NOTE(sean-k-mooney): we ignore this error as it
|
|
||||||
# is expected when the virtual function is not a NIC.
|
|
||||||
pass
|
|
||||||
if parent_ifname:
|
|
||||||
result['parent_ifname'] = parent_ifname
|
result['parent_ifname'] = parent_ifname
|
||||||
return result
|
return result
|
||||||
|
|
||||||
return {'dev_type': fields.PciDeviceType.STANDARD}
|
return {'dev_type': fields.PciDeviceType.STANDARD}
|
||||||
|
|
||||||
def _get_device_capabilities(device, address):
|
def _get_device_capabilities(
|
||||||
|
device_dict: dict,
|
||||||
|
device: 'libvirt.virNodeDevice',
|
||||||
|
net_devs: ty.List['libvirt.virNodeDevice']
|
||||||
|
) -> ty.Dict[str, ty.Dict[str, ty.Optional[ty.List[str]]]]:
|
||||||
"""Get PCI VF device's additional capabilities.
|
"""Get PCI VF device's additional capabilities.
|
||||||
|
|
||||||
If a PCI device is a virtual function, this function reads the PCI
|
If a PCI device is a virtual function, this function reads the PCI
|
||||||
parent's network capabilities (must be always a NIC device) and
|
parent's network capabilities (must be always a NIC device) and
|
||||||
appends this information to the device's dictionary.
|
appends this information to the device's dictionary.
|
||||||
"""
|
"""
|
||||||
if device.get('dev_type') == fields.PciDeviceType.SRIOV_VF:
|
if device_dict.get('dev_type') == fields.PciDeviceType.SRIOV_VF:
|
||||||
pcinet_info = self._get_pcinet_info(address)
|
pcinet_info = self._get_pcinet_info(device, net_devs)
|
||||||
if pcinet_info:
|
if pcinet_info:
|
||||||
return {'capabilities':
|
return {'capabilities': {'network': pcinet_info}}
|
||||||
{'network': pcinet_info.get('capabilities')}}
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
virtdev = self._host.device_lookup_by_name(devname)
|
xmlstr = dev.XMLDesc(0)
|
||||||
xmlstr = virtdev.XMLDesc(0)
|
|
||||||
cfgdev = vconfig.LibvirtConfigNodeDevice()
|
cfgdev = vconfig.LibvirtConfigNodeDevice()
|
||||||
cfgdev.parse_str(xmlstr)
|
cfgdev.parse_str(xmlstr)
|
||||||
|
|
||||||
|
@ -7003,8 +7008,8 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||||
|
|
||||||
# requirement by DataBase Model
|
# requirement by DataBase Model
|
||||||
device['label'] = 'label_%(vendor_id)s_%(product_id)s' % device
|
device['label'] = 'label_%(vendor_id)s_%(product_id)s' % device
|
||||||
device.update(_get_device_type(cfgdev, address))
|
device.update(_get_device_type(cfgdev, address, dev, net_devs))
|
||||||
device.update(_get_device_capabilities(device, address))
|
device.update(_get_device_capabilities(device, dev, net_devs))
|
||||||
return device
|
return device
|
||||||
|
|
||||||
def _get_pci_passthrough_devices(self):
|
def _get_pci_passthrough_devices(self):
|
||||||
|
@ -7022,28 +7027,13 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||||
:returns: a JSON string containing a list of the assignable PCI
|
:returns: a JSON string containing a list of the assignable PCI
|
||||||
devices information
|
devices information
|
||||||
"""
|
"""
|
||||||
# Bail early if we know we can't support `listDevices` to avoid
|
dev_flags = (libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_NET |
|
||||||
# repeated warnings within a periodic task
|
libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_PCI_DEV)
|
||||||
if not getattr(self, '_list_devices_supported', True):
|
devices = {dev.name(): dev for dev in
|
||||||
return jsonutils.dumps([])
|
self._host.list_all_devices(flags=dev_flags)}
|
||||||
|
net_devs = [dev for dev in devices.values() if "net" in dev.listCaps()]
|
||||||
try:
|
pci_info = [self._get_pcidev_info(name, dev, net_devs) for name, dev
|
||||||
dev_names = self._host.list_pci_devices() or []
|
in devices.items() if "pci" in dev.listCaps()]
|
||||||
except libvirt.libvirtError as ex:
|
|
||||||
error_code = ex.get_error_code()
|
|
||||||
if error_code == libvirt.VIR_ERR_NO_SUPPORT:
|
|
||||||
self._list_devices_supported = False
|
|
||||||
LOG.warning("URI %(uri)s does not support "
|
|
||||||
"listDevices: %(error)s",
|
|
||||||
{'uri': self._uri(),
|
|
||||||
'error': encodeutils.exception_to_unicode(ex)})
|
|
||||||
return jsonutils.dumps([])
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
pci_info = []
|
|
||||||
for name in dev_names:
|
|
||||||
pci_info.append(self._get_pcidev_info(name))
|
|
||||||
|
|
||||||
return jsonutils.dumps(pci_info)
|
return jsonutils.dumps(pci_info)
|
||||||
|
|
||||||
|
|
|
@ -31,8 +31,10 @@ from collections import defaultdict
|
||||||
import inspect
|
import inspect
|
||||||
import operator
|
import operator
|
||||||
import os
|
import os
|
||||||
|
import queue
|
||||||
import socket
|
import socket
|
||||||
import threading
|
import threading
|
||||||
|
import typing as ty
|
||||||
|
|
||||||
from eventlet import greenio
|
from eventlet import greenio
|
||||||
from eventlet import greenthread
|
from eventlet import greenthread
|
||||||
|
@ -59,7 +61,10 @@ from nova.virt.libvirt import guest as libvirt_guest
|
||||||
from nova.virt.libvirt import migration as libvirt_migrate
|
from nova.virt.libvirt import migration as libvirt_migrate
|
||||||
from nova.virt.libvirt import utils as libvirt_utils
|
from nova.virt.libvirt import utils as libvirt_utils
|
||||||
|
|
||||||
libvirt = None
|
if ty.TYPE_CHECKING:
|
||||||
|
import libvirt
|
||||||
|
else:
|
||||||
|
libvirt = None
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -92,7 +97,8 @@ class Host(object):
|
||||||
self._read_only = read_only
|
self._read_only = read_only
|
||||||
self._initial_connection = True
|
self._initial_connection = True
|
||||||
self._conn_event_handler = conn_event_handler
|
self._conn_event_handler = conn_event_handler
|
||||||
self._conn_event_handler_queue = six.moves.queue.Queue()
|
self._conn_event_handler_queue: queue.Queue[ty.Callable] = (
|
||||||
|
queue.Queue())
|
||||||
self._lifecycle_event_handler = lifecycle_event_handler
|
self._lifecycle_event_handler = lifecycle_event_handler
|
||||||
self._caps = None
|
self._caps = None
|
||||||
self._domain_caps = None
|
self._domain_caps = None
|
||||||
|
@ -100,7 +106,7 @@ class Host(object):
|
||||||
|
|
||||||
self._wrapped_conn = None
|
self._wrapped_conn = None
|
||||||
self._wrapped_conn_lock = threading.Lock()
|
self._wrapped_conn_lock = threading.Lock()
|
||||||
self._event_queue = None
|
self._event_queue: ty.Optional[queue.Queue[ty.Callable]] = None
|
||||||
|
|
||||||
self._events_delayed = {}
|
self._events_delayed = {}
|
||||||
# Note(toabctl): During a reboot of a domain, STOPPED and
|
# Note(toabctl): During a reboot of a domain, STOPPED and
|
||||||
|
@ -320,9 +326,14 @@ class Host(object):
|
||||||
# Process as many events as possible without
|
# Process as many events as possible without
|
||||||
# blocking
|
# blocking
|
||||||
last_close_event = None
|
last_close_event = None
|
||||||
|
# required for mypy
|
||||||
|
if self._event_queue is None:
|
||||||
|
return
|
||||||
while not self._event_queue.empty():
|
while not self._event_queue.empty():
|
||||||
try:
|
try:
|
||||||
event = self._event_queue.get(block=False)
|
event_type = ty.Union[
|
||||||
|
virtevent.LifecycleEvent, ty.Mapping[str, ty.Any]]
|
||||||
|
event: event_type = self._event_queue.get(block=False)
|
||||||
if isinstance(event, virtevent.LifecycleEvent):
|
if isinstance(event, virtevent.LifecycleEvent):
|
||||||
# call possibly with delay
|
# call possibly with delay
|
||||||
self._event_emit_delayed(event)
|
self._event_emit_delayed(event)
|
||||||
|
@ -809,7 +820,7 @@ class Host(object):
|
||||||
if self._domain_caps:
|
if self._domain_caps:
|
||||||
return self._domain_caps
|
return self._domain_caps
|
||||||
|
|
||||||
domain_caps = defaultdict(dict)
|
domain_caps: ty.Dict = defaultdict(dict)
|
||||||
caps = self.get_capabilities()
|
caps = self.get_capabilities()
|
||||||
virt_type = CONF.libvirt.virt_type
|
virt_type = CONF.libvirt.virt_type
|
||||||
|
|
||||||
|
@ -1183,6 +1194,19 @@ class Host(object):
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
def list_all_devices(
|
||||||
|
self, flags: int = 0) -> ty.List['libvirt.virNodeDevice']:
|
||||||
|
"""Lookup devices.
|
||||||
|
|
||||||
|
:param flags: a bitmask of flags to filter the returned devices.
|
||||||
|
:returns: a list of virNodeDevice xml strings.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.get_connection().listAllDevices(flags) or []
|
||||||
|
except libvirt.libvirtError as ex:
|
||||||
|
LOG.warning(ex)
|
||||||
|
return []
|
||||||
|
|
||||||
def compare_cpu(self, xmlDesc, flags=0):
|
def compare_cpu(self, xmlDesc, flags=0):
|
||||||
"""Compares the given CPU description with the host CPU."""
|
"""Compares the given CPU description with the host CPU."""
|
||||||
return self.get_connection().compareCPU(xmlDesc, flags)
|
return self.get_connection().compareCPU(xmlDesc, flags)
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
Since the 16.0.0 (Pike) release, nova has collected NIC feature
|
||||||
|
flags via libvirt. To look up the NIC feature flags for a whitelisted
|
||||||
|
PCI device the nova libvirt driver computed the libvirt nodedev name
|
||||||
|
by rendering a format string using the netdev name associated with
|
||||||
|
the interface and its current MAC address. In some environments the
|
||||||
|
libvirt nodedev list can become out of sync with the current MAC address
|
||||||
|
assigned to a netdev and as a result the nodedev look up can fail.
|
||||||
|
Nova now uses PCI addresses, rather than MAC addresses, to look up these
|
||||||
|
PCI network devices.
|
Loading…
Reference in New Issue