Skip to content

Commit cd03bbc

Browse files
Balazs Gibizergibizer
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 Change-Id: Iad5e70b43a65c076134e1874cb8e75d1ba214fde
1 parent b8cc570 commit cd03bbc

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
@@ -10929,6 +10929,9 @@ def _update_migrate_vifs_profile_with_pci(self,
1092910929
if profile.get('vf_num'):
1093010930
profile['vf_num'] = pci_dev.sriov_cap['vf_num']
1093110931

10932+
if pci_dev.mac_address:
10933+
profile['device_mac_address'] = pci_dev.mac_address
10934+
1093210935
mig_vif.profile = profile
1093310936
LOG.debug("Updating migrate VIF profile for port %(port_id)s:"
1093410937
"%(profile)s", {'port_id': port_id,

nova/network/neutron.py

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

@@ -1597,6 +1602,18 @@ def _get_pci_device_profile(self, pci_dev):
15971602
if pci_dev.dev_type == obj_fields.PciDeviceType.SRIOV_VF:
15981603
dev_profile.update(
15991604
self._get_vf_pci_device_profile(pci_dev))
1605+
1606+
if pci_dev.dev_type == obj_fields.PciDeviceType.SRIOV_PF:
1607+
# In general the MAC address information flows fom the neutron
1608+
# port to the device in the backend. Except for direct-physical
1609+
# ports. In that case the MAC address flows from the physical
1610+
# device, the PF, to the neutron port. So when such a port is
1611+
# being bound to a host the port's MAC address needs to be
1612+
# updated. Nova needs to put the new MAC into the binding
1613+
# profile.
1614+
if pci_dev.mac_address:
1615+
dev_profile['device_mac_address'] = pci_dev.mac_address
1616+
16001617
return dev_profile
16011618

16021619
raise exception.PciDeviceNotFound(node_id=pci_dev.compute_node_id,
@@ -3638,11 +3655,10 @@ def _get_pci_mapping_for_migration(self, instance, migration):
36383655
revert = migration and migration.status == 'reverted'
36393656
return instance.migration_context.get_pci_mapping_for_migration(revert)
36403657

3641-
def _get_port_pci_dev(self, context, instance, port):
3658+
def _get_port_pci_dev(self, instance, port):
36423659
"""Find the PCI device corresponding to the port.
36433660
Assumes the port is an SRIOV one.
36443661
3645-
:param context: The request context.
36463662
:param instance: The instance to which the port is attached.
36473663
:param port: The Neutron port, as obtained from the Neutron API
36483664
JSON form.
@@ -3730,15 +3746,14 @@ def _update_port_binding_for_instance(
37303746
raise exception.PortUpdateFailed(port_id=p['id'],
37313747
reason=_("Unable to correlate PCI slot %s") %
37323748
pci_slot)
3733-
# NOTE(artom) If migration is None, this is an unshevle, and we
3734-
# need to figure out the pci_slot from the InstancePCIRequest
3735-
# and PciDevice objects.
3749+
# NOTE(artom) If migration is None, this is an unshelve, and we
3750+
# need to figure out the pci related binding information from
3751+
# the InstancePCIRequest and PciDevice objects.
37363752
else:
3737-
pci_dev = self._get_port_pci_dev(context, instance, p)
3753+
pci_dev = self._get_port_pci_dev(instance, p)
37383754
if pci_dev:
37393755
binding_profile.update(
3740-
self._get_pci_device_profile(pci_dev)
3741-
)
3756+
self._get_pci_device_profile(pci_dev))
37423757
updates[constants.BINDING_PROFILE] = binding_profile
37433758

37443759
# 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})
@@ -542,6 +551,13 @@ def sriov_cap(self):
542551
caps = jsonutils.loads(caps_json)
543552
return caps.get('sriov', {})
544553

554+
@property
555+
def mac_address(self):
556+
"""The MAC address of the PF physical device or None if the device is
557+
not a PF or if the MAC is not available.
558+
"""
559+
return self.extra_info.get('mac_address')
560+
545561

546562
@base.NovaObjectRegistry.register
547563
class PciDeviceList(base.ObjectListBase, base.NovaObject):
@@ -581,3 +597,6 @@ def get_by_parent_address(cls, context, node_id, parent_addr):
581597
parent_addr)
582598
return base.obj_make_list(context, cls(context), objects.PciDevice,
583599
db_dev_list)
600+
601+
def __repr__(self):
602+
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)