Skip to content

Commit 0150ed9

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Record SRIOV PF MAC in the binding profile" into stable/xena
2 parents 2561237 + a340630 commit 0150ed9

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
@@ -10857,6 +10857,8 @@ def _update_migrate_vifs_profile_with_pci(self,
1085710857
profile['pci_slot'] = pci_dev.address
1085810858
profile['pci_vendor_info'] = ':'.join([pci_dev.vendor_id,
1085910859
pci_dev.product_id])
10860+
if pci_dev.mac_address:
10861+
profile['device_mac_address'] = pci_dev.mac_address
1086010862
mig_vif.profile = profile
1086110863
LOG.debug("Updating migrate VIF profile for port %(port_id)s:"
1086210864
"%(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)