Skip to content

Commit a340630

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/network/neutron.py nova/objects/pci_device.py nova/tests/fixtures/libvirt.py nova/tests/unit/network/test_neutron.py nova/tests/unit/virt/libvirt/test_host.py nova/virt/fake.py nova/virt/libvirt/host.py Most of the conlficts are due to the lack of the vpd feature and I83a128a260acdd8bf78fede566af6881b8b82a9c the addtional unit tests in test_neutron and test_compute_mgr are form the vpd feature but have been modified to drop assertions related to the vpd feature while keeping the assertions related to SRIOV PF MAC adress binding. Change-Id: Iad5e70b43a65c076134e1874cb8e75d1ba214fde (cherry picked from commit cd03bbc) (cherry picked from commit 8133770)
1 parent e7c2e55 commit a340630

File tree

15 files changed

+907
-135
lines changed

15 files changed

+907
-135
lines changed

nova/compute/manager.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10820,6 +10820,8 @@ def _update_migrate_vifs_profile_with_pci(self,
1082010820
profile['pci_slot'] = pci_dev.address
1082110821
profile['pci_vendor_info'] = ':'.join([pci_dev.vendor_id,
1082210822
pci_dev.product_id])
10823+
if pci_dev.mac_address:
10824+
profile['device_mac_address'] = pci_dev.mac_address
1082310825
mig_vif.profile = profile
1082410826
LOG.debug("Updating migrate VIF profile for port %(port_id)s:"
1082510827
"%(profile)s", {'port_id': port_id,

nova/compute/resource_tracker.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,12 @@ def _move_claim(
328328
migration_id=migration.id,
329329
old_numa_topology=instance.numa_topology,
330330
new_numa_topology=claim.claimed_numa_topology,
331-
old_pci_devices=instance.pci_devices,
331+
# NOTE(gibi): the _update_usage_from_migration call below appends
332+
# the newly claimed pci devices to the instance.pci_devices list
333+
# to keep the migration context independent we need to make a copy
334+
# that list here. We need a deep copy as we need to duplicate
335+
# the instance.pci_devices.objects list
336+
old_pci_devices=copy.deepcopy(instance.pci_devices),
332337
new_pci_devices=claimed_pci_devices,
333338
old_pci_requests=instance.pci_requests,
334339
new_pci_requests=new_pci_requests,

nova/network/neutron.py

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,8 @@ def _unbind_ports(self, context, ports,
667667
# for the physical device but don't want to overwrite the other
668668
# information in the binding profile.
669669
for profile_key in ('pci_vendor_info', 'pci_slot',
670-
constants.ALLOCATION, 'arq_uuid'):
670+
constants.ALLOCATION, 'arq_uuid',
671+
'device_mac_address'):
671672
if profile_key in port_profile:
672673
del port_profile[profile_key]
673674
port_req_body['port'][constants.BINDING_PROFILE] = port_profile
@@ -1286,6 +1287,10 @@ def _update_ports_for_instance(self, context, instance, neutron,
12861287
network=network, neutron=neutron,
12871288
bind_host_id=bind_host_id,
12881289
port_arq=port_arq)
1290+
# NOTE(gibi): Remove this once we are sure that the fix for
1291+
# bug 1942329 is always present in the deployed neutron. The
1292+
# _populate_neutron_extension_values() call above already
1293+
# populated this MAC to the binding profile instead.
12891294
self._populate_pci_mac_address(instance,
12901295
request.pci_request_id, port_req_body)
12911296

@@ -1506,11 +1511,27 @@ def delete_port_binding(self, context, port_id, host):
15061511
def _get_pci_device_profile(self, pci_dev):
15071512
dev_spec = self.pci_whitelist.get_devspec(pci_dev)
15081513
if dev_spec:
1509-
return {'pci_vendor_info': "%s:%s" %
1510-
(pci_dev.vendor_id, pci_dev.product_id),
1511-
'pci_slot': pci_dev.address,
1512-
'physical_network':
1513-
dev_spec.get_tags().get('physical_network')}
1514+
dev_profile = {
1515+
'pci_vendor_info': "%s:%s"
1516+
% (pci_dev.vendor_id, pci_dev.product_id),
1517+
'pci_slot': pci_dev.address,
1518+
'physical_network': dev_spec.get_tags().get(
1519+
'physical_network'
1520+
),
1521+
}
1522+
if pci_dev.dev_type == obj_fields.PciDeviceType.SRIOV_PF:
1523+
# In general the MAC address information flows fom the neutron
1524+
# port to the device in the backend. Except for direct-physical
1525+
# ports. In that case the MAC address flows from the physical
1526+
# device, the PF, to the neutron port. So when such a port is
1527+
# being bound to a host the port's MAC address needs to be
1528+
# updated. Nova needs to put the new MAC into the binding
1529+
# profile.
1530+
if pci_dev.mac_address:
1531+
dev_profile['device_mac_address'] = pci_dev.mac_address
1532+
1533+
return dev_profile
1534+
15141535
raise exception.PciDeviceNotFound(node_id=pci_dev.compute_node_id,
15151536
address=pci_dev.address)
15161537

@@ -3512,11 +3533,10 @@ def _get_pci_mapping_for_migration(self, instance, migration):
35123533
migration.get('status') == 'reverted')
35133534
return instance.migration_context.get_pci_mapping_for_migration(revert)
35143535

3515-
def _get_port_pci_dev(self, context, instance, port):
3536+
def _get_port_pci_dev(self, instance, port):
35163537
"""Find the PCI device corresponding to the port.
35173538
Assumes the port is an SRIOV one.
35183539
3519-
:param context: The request context.
35203540
:param instance: The instance to which the port is attached.
35213541
:param port: The Neutron port, as obtained from the Neutron API
35223542
JSON form.
@@ -3604,15 +3624,14 @@ def _update_port_binding_for_instance(
36043624
raise exception.PortUpdateFailed(port_id=p['id'],
36053625
reason=_("Unable to correlate PCI slot %s") %
36063626
pci_slot)
3607-
# NOTE(artom) If migration is None, this is an unshevle, and we
3608-
# need to figure out the pci_slot from the InstancePCIRequest
3609-
# and PciDevice objects.
3627+
# NOTE(artom) If migration is None, this is an unshelve, and we
3628+
# need to figure out the pci related binding information from
3629+
# the InstancePCIRequest and PciDevice objects.
36103630
else:
3611-
pci_dev = self._get_port_pci_dev(context, instance, p)
3631+
pci_dev = self._get_port_pci_dev(instance, p)
36123632
if pci_dev:
36133633
binding_profile.update(
3614-
self._get_pci_device_profile(pci_dev)
3615-
)
3634+
self._get_pci_device_profile(pci_dev))
36163635
updates[constants.BINDING_PROFILE] = binding_profile
36173636

36183637
# 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})
@@ -511,6 +520,13 @@ def free(self, instance=None):
511520
def is_available(self):
512521
return self.status == fields.PciDeviceStatus.AVAILABLE
513522

523+
@property
524+
def mac_address(self):
525+
"""The MAC address of the PF physical device or None if the device is
526+
not a PF or if the MAC is not available.
527+
"""
528+
return self.extra_info.get('mac_address')
529+
514530

515531
@base.NovaObjectRegistry.register
516532
class PciDeviceList(base.ObjectListBase, base.NovaObject):
@@ -550,3 +566,6 @@ def get_by_parent_address(cls, context, node_id, parent_addr):
550566
parent_addr)
551567
return base.obj_make_list(context, cls(context), objects.PciDevice,
552568
db_dev_list)
569+
570+
def __repr__(self):
571+
return f"PciDeviceList(objects={[repr(obj) for obj in self.objects]})"

nova/tests/fixtures/libvirt.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ def __init__(
299299
self, dev_type, bus, slot, function, iommu_group, numa_node, *,
300300
vf_ratio=None, multiple_gpu_types=False, generic_types=False,
301301
parent=None, vend_id=None, vend_name=None, prod_id=None,
302-
prod_name=None, driver_name=None,
302+
prod_name=None, driver_name=None, mac_address=None,
303303
):
304304
"""Populate pci devices
305305
@@ -321,6 +321,8 @@ def __init__(
321321
:param prod_id: (str) The product ID.
322322
:param prod_name: (str) The product name.
323323
:param driver_name: (str) The driver name.
324+
:param mac_address: (str) The MAC of the device.
325+
Used in case of SRIOV PFs
324326
"""
325327

326328
self.dev_type = dev_type
@@ -339,6 +341,7 @@ def __init__(
339341
self.prod_id = prod_id
340342
self.prod_name = prod_name
341343
self.driver_name = driver_name
344+
self.mac_address = mac_address
342345

343346
self.generate_xml()
344347

@@ -352,7 +355,9 @@ def generate_xml(self, skip_capability=False):
352355
assert not self.vf_ratio, 'vf_ratio does not apply for PCI devices'
353356

354357
if self.dev_type in ('PF', 'VF'):
355-
assert self.vf_ratio, 'require vf_ratio for PFs and VFs'
358+
assert (
359+
self.vf_ratio is not None
360+
), 'require vf_ratio for PFs and VFs'
356361

357362
if self.dev_type == 'VF':
358363
assert self.parent, 'require parent for VFs'
@@ -460,6 +465,10 @@ def generate_xml(self, skip_capability=False):
460465
def XMLDesc(self, flags):
461466
return self.pci_device
462467

468+
@property
469+
def address(self):
470+
return "0000:%02x:%02x.%1x" % (self.bus, self.slot, self.function)
471+
463472

464473
# TODO(stephenfin): Remove all of these HostFooDevicesInfo objects in favour of
465474
# a unified devices object
@@ -572,7 +581,7 @@ def add_device(
572581
self, dev_type, bus, slot, function, iommu_group, numa_node,
573582
vf_ratio=None, multiple_gpu_types=False, generic_types=False,
574583
parent=None, vend_id=None, vend_name=None, prod_id=None,
575-
prod_name=None, driver_name=None,
584+
prod_name=None, driver_name=None, mac_address=None,
576585
):
577586
pci_dev_name = _get_libvirt_nodedev_name(bus, slot, function)
578587

@@ -593,7 +602,9 @@ def add_device(
593602
vend_name=vend_name,
594603
prod_id=prod_id,
595604
prod_name=prod_name,
596-
driver_name=driver_name)
605+
driver_name=driver_name,
606+
mac_address=mac_address,
607+
)
597608
self.devices[pci_dev_name] = dev
598609
return dev
599610

@@ -612,6 +623,13 @@ def get_all_mdev_capable_devices(self):
612623
return [dev for dev in self.devices
613624
if self.devices[dev].is_capable_of_mdevs]
614625

626+
def get_pci_address_mac_mapping(self):
627+
return {
628+
device.address: device.mac_address
629+
for dev_addr, device in self.devices.items()
630+
if device.mac_address
631+
}
632+
615633

616634
class FakeMdevDevice(object):
617635
template = """
@@ -2141,6 +2159,15 @@ class LibvirtFixture(fixtures.Fixture):
21412159
"""
21422160
def __init__(self, stub_os_vif=True):
21432161
self.stub_os_vif = stub_os_vif
2162+
self.pci_address_to_mac_map = collections.defaultdict(
2163+
lambda: '52:54:00:1e:59:c6')
2164+
2165+
def update_sriov_mac_address_mapping(self, pci_address_to_mac_map):
2166+
self.pci_address_to_mac_map.update(pci_address_to_mac_map)
2167+
2168+
def fake_get_mac_by_pci_address(self, pci_addr, pf_interface=False):
2169+
res = self.pci_address_to_mac_map[pci_addr]
2170+
return res
21442171

21452172
def setUp(self):
21462173
super().setUp()
@@ -2162,6 +2189,10 @@ def setUp(self):
21622189
'nova.pci.utils.get_ifname_by_pci_address',
21632190
return_value='fake_pf_interface_name'))
21642191

2192+
self.useFixture(fixtures.MockPatch(
2193+
'nova.pci.utils.get_mac_by_pci_address',
2194+
new=self.fake_get_mac_by_pci_address))
2195+
21652196
# libvirt calls out to sysfs to get the vfs ID during macvtap plug
21662197
self.useFixture(fixtures.MockPatch(
21672198
'nova.pci.utils.get_vf_num_by_pci_address', return_value=1))

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',
@@ -361,6 +367,22 @@ class LibvirtNeutronFixture(nova_fixtures.NeutronFixture):
361367
'binding:vnic_type': 'direct',
362368
}
363369

370+
network_4_port_pf = {
371+
'id': 'c6f51315-9202-416f-9e2f-eb78b3ac36d9',
372+
'network_id': network_4['id'],
373+
'status': 'ACTIVE',
374+
'mac_address': 'b5:bc:2e:e7:51:01',
375+
'fixed_ips': [
376+
{
377+
'ip_address': '192.168.4.8',
378+
'subnet_id': subnet_4['id']
379+
}
380+
],
381+
'binding:vif_details': {'vlan': 42},
382+
'binding:vif_type': 'hostdev_physical',
383+
'binding:vnic_type': 'direct-physical',
384+
}
385+
364386
def __init__(self, test):
365387
super(LibvirtNeutronFixture, self).__init__(test)
366388
self._networks = {

0 commit comments

Comments
 (0)