Skip to content

Commit 8133770

Browse files
Balazs GibizerSeanMooney
authored andcommitted
Record SRIOV PF MAC in the binding profile
Today Nova updates the mac_address of a direct-physical port to reflect the MAC address of the physical device the port is bound to. But this can only be done before the port is bound. However during migration Nova does not update the MAC when the port is bound to a different physical device on the destination host. This patch extends the libvirt virt driver to provide the MAC address of the PF in the pci_info returned to the resource tracker. This information will be then persisted in the extra_info field of the PciDevice object. Then the port update logic during migration, resize, live migration, evacuation and unshelve is also extended to record the MAC of physical device in the port binding profile according to the device on the destination host. The related neutron change Ib0638f5db69cb92daf6932890cb89e83cf84f295 uses this info from the binding profile to update the mac_address field of the port when the binding is activated. Closes-Bug: #1942329 Conflicts: nova/objects/pci_device.py nova/virt/libvirt/host.py Change-Id: Iad5e70b43a65c076134e1874cb8e75d1ba214fde (cherry picked from commit cd03bbc)
1 parent 683cbd0 commit 8133770

File tree

14 files changed

+807
-102
lines changed

14 files changed

+807
-102
lines changed

nova/compute/manager.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10968,6 +10968,9 @@ def _update_migrate_vifs_profile_with_pci(self,
1096810968
profile['vf_num'] = pci_utils.get_vf_num_by_pci_address(
1096910969
pci_dev.address)
1097010970

10971+
if pci_dev.mac_address:
10972+
profile['device_mac_address'] = pci_dev.mac_address
10973+
1097110974
mig_vif.profile = profile
1097210975
LOG.debug("Updating migrate VIF profile for port %(port_id)s:"
1097310976
"%(profile)s", {'port_id': port_id,

nova/network/neutron.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,8 @@ def _unbind_ports(self, context, ports,
684684
for profile_key in ('pci_vendor_info', 'pci_slot',
685685
constants.ALLOCATION, 'arq_uuid',
686686
'physical_network', 'card_serial_number',
687-
'vf_num', 'pf_mac_address'):
687+
'vf_num', 'pf_mac_address',
688+
'device_mac_address'):
688689
if profile_key in port_profile:
689690
del port_profile[profile_key]
690691
port_req_body['port'][constants.BINDING_PROFILE] = port_profile
@@ -1307,6 +1308,10 @@ def _update_ports_for_instance(self, context, instance, neutron,
13071308
network=network, neutron=neutron,
13081309
bind_host_id=bind_host_id,
13091310
port_arq=port_arq)
1311+
# NOTE(gibi): Remove this once we are sure that the fix for
1312+
# bug 1942329 is always present in the deployed neutron. The
1313+
# _populate_neutron_extension_values() call above already
1314+
# populated this MAC to the binding profile instead.
13101315
self._populate_pci_mac_address(instance,
13111316
request.pci_request_id, port_req_body)
13121317

@@ -1622,6 +1627,18 @@ def _get_pci_device_profile(self, pci_dev):
16221627
if pci_dev.dev_type == obj_fields.PciDeviceType.SRIOV_VF:
16231628
dev_profile.update(
16241629
self._get_vf_pci_device_profile(pci_dev))
1630+
1631+
if pci_dev.dev_type == obj_fields.PciDeviceType.SRIOV_PF:
1632+
# In general the MAC address information flows fom the neutron
1633+
# port to the device in the backend. Except for direct-physical
1634+
# ports. In that case the MAC address flows from the physical
1635+
# device, the PF, to the neutron port. So when such a port is
1636+
# being bound to a host the port's MAC address needs to be
1637+
# updated. Nova needs to put the new MAC into the binding
1638+
# profile.
1639+
if pci_dev.mac_address:
1640+
dev_profile['device_mac_address'] = pci_dev.mac_address
1641+
16251642
return dev_profile
16261643

16271644
raise exception.PciDeviceNotFound(node_id=pci_dev.compute_node_id,
@@ -3664,11 +3681,10 @@ def _get_pci_mapping_for_migration(self, instance, migration):
36643681
migration.get('status') == 'reverted')
36653682
return instance.migration_context.get_pci_mapping_for_migration(revert)
36663683

3667-
def _get_port_pci_dev(self, context, instance, port):
3684+
def _get_port_pci_dev(self, instance, port):
36683685
"""Find the PCI device corresponding to the port.
36693686
Assumes the port is an SRIOV one.
36703687
3671-
:param context: The request context.
36723688
:param instance: The instance to which the port is attached.
36733689
:param port: The Neutron port, as obtained from the Neutron API
36743690
JSON form.
@@ -3756,15 +3772,14 @@ def _update_port_binding_for_instance(
37563772
raise exception.PortUpdateFailed(port_id=p['id'],
37573773
reason=_("Unable to correlate PCI slot %s") %
37583774
pci_slot)
3759-
# NOTE(artom) If migration is None, this is an unshevle, and we
3760-
# need to figure out the pci_slot from the InstancePCIRequest
3761-
# and PciDevice objects.
3775+
# NOTE(artom) If migration is None, this is an unshelve, and we
3776+
# need to figure out the pci related binding information from
3777+
# the InstancePCIRequest and PciDevice objects.
37623778
else:
3763-
pci_dev = self._get_port_pci_dev(context, instance, p)
3779+
pci_dev = self._get_port_pci_dev(instance, p)
37643780
if pci_dev:
37653781
binding_profile.update(
3766-
self._get_pci_device_profile(pci_dev)
3767-
)
3782+
self._get_pci_device_profile(pci_dev))
37683783
updates[constants.BINDING_PROFILE] = binding_profile
37693784

37703785
# NOTE(gibi): during live migration the conductor already sets the

nova/objects/pci_device.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,12 @@ def obj_make_compatible(self, primitive, target_version):
148148
reason='dev_type=%s not supported in version %s' % (
149149
dev_type, target_version))
150150

151+
def __repr__(self):
152+
return (
153+
f'PciDevice(address={self.address}, '
154+
f'compute_node_id={self.compute_node_id})'
155+
)
156+
151157
def update_device(self, dev_dict):
152158
"""Sync the content from device dictionary to device object.
153159
@@ -175,6 +181,9 @@ def update_device(self, dev_dict):
175181
# NOTE(ralonsoh): list of parameters currently added to
176182
# "extra_info" dict:
177183
# - "capabilities": dict of (strings/list of strings)
184+
# - "parent_ifname": the netdev name of the parent (PF)
185+
# device of a VF
186+
# - "mac_address": the MAC address of the PF
178187
extra_info = self.extra_info
179188
data = v if isinstance(v, str) else jsonutils.dumps(v)
180189
extra_info.update({k: data})
@@ -566,6 +575,13 @@ def card_serial_number(self):
566575
caps = jsonutils.loads(caps_json)
567576
return caps.get('vpd', {}).get('card_serial_number')
568577

578+
@property
579+
def mac_address(self):
580+
"""The MAC address of the PF physical device or None if the device is
581+
not a PF or if the MAC is not available.
582+
"""
583+
return self.extra_info.get('mac_address')
584+
569585

570586
@base.NovaObjectRegistry.register
571587
class PciDeviceList(base.ObjectListBase, base.NovaObject):
@@ -605,3 +621,6 @@ def get_by_parent_address(cls, context, node_id, parent_addr):
605621
parent_addr)
606622
return base.obj_make_list(context, cls(context), objects.PciDevice,
607623
db_dev_list)
624+
625+
def __repr__(self):
626+
return f"PciDeviceList(objects={[repr(obj) for obj in self.objects]})"

nova/tests/fixtures/libvirt.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ def __init__(
309309
self, dev_type, bus, slot, function, iommu_group, numa_node, *,
310310
vf_ratio=None, multiple_gpu_types=False, generic_types=False,
311311
parent=None, vend_id=None, vend_name=None, prod_id=None,
312-
prod_name=None, driver_name=None, vpd_fields=None
312+
prod_name=None, driver_name=None, vpd_fields=None, mac_address=None,
313313
):
314314
"""Populate pci devices
315315
@@ -331,6 +331,8 @@ def __init__(
331331
:param prod_id: (str) The product ID.
332332
:param prod_name: (str) The product name.
333333
:param driver_name: (str) The driver name.
334+
:param mac_address: (str) The MAC of the device.
335+
Used in case of SRIOV PFs
334336
"""
335337

336338
self.dev_type = dev_type
@@ -349,6 +351,7 @@ def __init__(
349351
self.prod_id = prod_id
350352
self.prod_name = prod_name
351353
self.driver_name = driver_name
354+
self.mac_address = mac_address
352355

353356
self.vpd_fields = vpd_fields
354357

@@ -364,7 +367,9 @@ def generate_xml(self, skip_capability=False):
364367
assert not self.vf_ratio, 'vf_ratio does not apply for PCI devices'
365368

366369
if self.dev_type in ('PF', 'VF'):
367-
assert self.vf_ratio, 'require vf_ratio for PFs and VFs'
370+
assert (
371+
self.vf_ratio is not None
372+
), 'require vf_ratio for PFs and VFs'
368373

369374
if self.dev_type == 'VF':
370375
assert self.parent, 'require parent for VFs'
@@ -497,6 +502,10 @@ def format_vpd_cap(self):
497502
def XMLDesc(self, flags):
498503
return self.pci_device
499504

505+
@property
506+
def address(self):
507+
return "0000:%02x:%02x.%1x" % (self.bus, self.slot, self.function)
508+
500509

501510
# TODO(stephenfin): Remove all of these HostFooDevicesInfo objects in favour of
502511
# a unified devices object
@@ -609,7 +618,7 @@ def add_device(
609618
self, dev_type, bus, slot, function, iommu_group, numa_node,
610619
vf_ratio=None, multiple_gpu_types=False, generic_types=False,
611620
parent=None, vend_id=None, vend_name=None, prod_id=None,
612-
prod_name=None, driver_name=None, vpd_fields=None,
621+
prod_name=None, driver_name=None, vpd_fields=None, mac_address=None,
613622
):
614623
pci_dev_name = _get_libvirt_nodedev_name(bus, slot, function)
615624

@@ -632,6 +641,7 @@ def add_device(
632641
prod_name=prod_name,
633642
driver_name=driver_name,
634643
vpd_fields=vpd_fields,
644+
mac_address=mac_address,
635645
)
636646
self.devices[pci_dev_name] = dev
637647
return dev
@@ -651,6 +661,13 @@ def get_all_mdev_capable_devices(self):
651661
return [dev for dev in self.devices
652662
if self.devices[dev].is_capable_of_mdevs]
653663

664+
def get_pci_address_mac_mapping(self):
665+
return {
666+
device.address: device.mac_address
667+
for dev_addr, device in self.devices.items()
668+
if device.mac_address
669+
}
670+
654671

655672
class FakeMdevDevice(object):
656673
template = """
@@ -2182,6 +2199,15 @@ class LibvirtFixture(fixtures.Fixture):
21822199

21832200
def __init__(self, stub_os_vif=True):
21842201
self.stub_os_vif = stub_os_vif
2202+
self.pci_address_to_mac_map = collections.defaultdict(
2203+
lambda: '52:54:00:1e:59:c6')
2204+
2205+
def update_sriov_mac_address_mapping(self, pci_address_to_mac_map):
2206+
self.pci_address_to_mac_map.update(pci_address_to_mac_map)
2207+
2208+
def fake_get_mac_by_pci_address(self, pci_addr, pf_interface=False):
2209+
res = self.pci_address_to_mac_map[pci_addr]
2210+
return res
21852211

21862212
def setUp(self):
21872213
super().setUp()
@@ -2205,7 +2231,7 @@ def setUp(self):
22052231

22062232
self.useFixture(fixtures.MockPatch(
22072233
'nova.pci.utils.get_mac_by_pci_address',
2208-
return_value='52:54:00:1e:59:c6'))
2234+
new=self.fake_get_mac_by_pci_address))
22092235

22102236
# libvirt calls out to sysfs to get the vfs ID during macvtap plug
22112237
self.useFixture(fixtures.MockPatch(

nova/tests/functional/libvirt/base.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def setUp(self):
4242
super(ServersTestBase, self).setUp()
4343

4444
self.useFixture(nova_fixtures.LibvirtImageBackendFixture())
45-
self.useFixture(nova_fixtures.LibvirtFixture())
45+
self.libvirt = self.useFixture(nova_fixtures.LibvirtFixture())
4646
self.useFixture(nova_fixtures.OSBrickFixture())
4747

4848
self.useFixture(fixtures.MockPatch(
@@ -134,6 +134,12 @@ def _start_compute(hostname, host_info):
134134
host_info, pci_info, mdev_info, vdpa_info, libvirt_version,
135135
qemu_version, hostname,
136136
)
137+
# If the compute is configured with PCI devices then we need to
138+
# make sure that the stubs around sysfs has the MAC address
139+
# information for the PCI PF devices
140+
if pci_info:
141+
self.libvirt.update_sriov_mac_address_mapping(
142+
pci_info.get_pci_address_mac_mapping())
137143
# This is fun. Firstly we need to do a global'ish mock so we can
138144
# actually start the service.
139145
with mock.patch('nova.virt.libvirt.host.Host.get_connection',
@@ -392,6 +398,22 @@ class LibvirtNeutronFixture(nova_fixtures.NeutronFixture):
392398
'binding:vnic_type': 'remote-managed',
393399
}
394400

401+
network_4_port_pf = {
402+
'id': 'c6f51315-9202-416f-9e2f-eb78b3ac36d9',
403+
'network_id': network_4['id'],
404+
'status': 'ACTIVE',
405+
'mac_address': 'b5:bc:2e:e7:51:01',
406+
'fixed_ips': [
407+
{
408+
'ip_address': '192.168.4.8',
409+
'subnet_id': subnet_4['id']
410+
}
411+
],
412+
'binding:vif_details': {'vlan': 42},
413+
'binding:vif_type': 'hostdev_physical',
414+
'binding:vnic_type': 'direct-physical',
415+
}
416+
395417
def __init__(self, test):
396418
super(LibvirtNeutronFixture, self).__init__(test)
397419
self._networks = {

0 commit comments

Comments
 (0)