From 7e4f45df91f33fa8b75feec95e5636db06fda443 Mon Sep 17 00:00:00 2001 From: Alexey Stupnikov Date: Thu, 25 May 2023 21:23:32 +0200 Subject: [PATCH] Translate VF network capabilities to port binding Libvirt's node device driver accumulates and reports information about host devices. Network capabilities reported by node device driver for NIC contain information about HW offloads supported by this NIC. One of possible features reported by node device driver is switchdev: a NIC capability to implement VFs similar to actual HW switch ports (also referred to as SR-IOV OVS hardware offload). From Neutron perspective, vnic-type should be set to "direct" and "switchdev" capability should be added to port binding profile to enable HW offload (there are also configuration steps on compute hosts to tune NIC config). This patch was written to automatically translate "switchdev" from VF network capabilities reported by node device driver to Neutron port binding profile and allow user to skip manual step that requires admin privileges. Other capabilities are also translated: they are not used right now, but provide visibility and can be utilized later. Closes-bug: #2020813 Closes-bug: #2008238 Change-Id: I3b17f386325b8f42c0c374f766fb21c520161a59 (cherry picked from commit cef3b5ef2cc1fe983578e4966208cf95fdea5880) --- nova/network/neutron.py | 7 +++++++ nova/objects/pci_device.py | 7 +++++++ nova/tests/fixtures/libvirt_data.py | 1 + nova/tests/unit/network/test_neutron.py | 7 +++++-- nova/tests/unit/objects/test_pci_device.py | 10 ++++++++++ nova/tests/unit/virt/libvirt/test_host.py | 2 +- ...ilities_to_port_binding-48abbfe0ce2923cf.yaml | 16 ++++++++++++++++ 7 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/translate_vf_network_capabilities_to_port_binding-48abbfe0ce2923cf.yaml diff --git a/nova/network/neutron.py b/nova/network/neutron.py index eef2abe36414..e6d47c9a6b39 100644 --- a/nova/network/neutron.py +++ b/nova/network/neutron.py @@ -1601,6 +1601,13 @@ class API: 'pf_mac_address': pf_mac, 'vf_num': vf_num, }) + + # Update port binding capabilities using PCI device's network + # capabilities if they exist. + pci_net_caps = pci_dev.network_caps + if pci_net_caps: + vf_profile.update({'capabilities': pci_net_caps}) + return vf_profile def _get_pci_device_profile(self, pci_dev): diff --git a/nova/objects/pci_device.py b/nova/objects/pci_device.py index 14f9cca14a97..ab775b4554ac 100644 --- a/nova/objects/pci_device.py +++ b/nova/objects/pci_device.py @@ -589,6 +589,13 @@ class PciDevice(base.NovaPersistentObject, base.NovaObject): """ return self.extra_info.get('mac_address') + @property + def network_caps(self): + """PCI device network capabilities or empty list if not available""" + caps_json = self.extra_info.get('capabilities', '{}') + caps = jsonutils.loads(caps_json) + return caps.get('network', []) + @base.NovaObjectRegistry.register class PciDeviceList(base.ObjectListBase, base.NovaObject): diff --git a/nova/tests/fixtures/libvirt_data.py b/nova/tests/fixtures/libvirt_data.py index f022860f615d..f921a5e2df37 100644 --- a/nova/tests/fixtures/libvirt_data.py +++ b/nova/tests/fixtures/libvirt_data.py @@ -2182,6 +2182,7 @@ _fake_NodeDevXml = { + """, # noqa:E501 diff --git a/nova/tests/unit/network/test_neutron.py b/nova/tests/unit/network/test_neutron.py index c551191e4cf8..0789022cfae1 100644 --- a/nova/tests/unit/network/test_neutron.py +++ b/nova/tests/unit/network/test_neutron.py @@ -8144,17 +8144,20 @@ class TestAPIPortbinding(TestAPIBase): 'pf_mac_address': '52:54:00:1e:59:c6', 'vf_num': 1, }, + 'network_caps': ['gso', 'sg', 'tso', 'tx'], 'dev_type': obj_fields.PciDeviceType.SRIOV_VF, } PciDevice = collections.namedtuple('PciDevice', ['vendor_id', 'product_id', 'address', 'card_serial_number', 'sriov_cap', - 'dev_type', 'parent_addr']) + 'dev_type', 'parent_addr', + 'network_caps']) mydev = PciDevice(**pci_dev) self.assertEqual(self.api._get_vf_pci_device_profile(mydev), {'pf_mac_address': '52:54:00:1e:59:c6', 'vf_num': 1, - 'card_serial_number': 'MT2113X00000'}) + 'card_serial_number': 'MT2113X00000', + 'capabilities': ['gso', 'sg', 'tso', 'tx']}) @mock.patch.object( neutronapi.API, '_get_vf_pci_device_profile', diff --git a/nova/tests/unit/objects/test_pci_device.py b/nova/tests/unit/objects/test_pci_device.py index 1e971c5a2149..e0570b69a8ef 100644 --- a/nova/tests/unit/objects/test_pci_device.py +++ b/nova/tests/unit/objects/test_pci_device.py @@ -171,6 +171,16 @@ class _TestPciDeviceObject(object): self.pci_device = pci_device.PciDevice.create(None, self.dev_dict) self.assertEqual(self.pci_device.card_serial_number, '42') + def test_pci_device_extra_info_network_capabilities(self): + self.dev_dict = copy.copy(dev_dict) + self.pci_device = pci_device.PciDevice.create(None, self.dev_dict) + self.assertEqual(self.pci_device.network_caps, []) + + self.dev_dict = copy.copy(dev_dict) + self.dev_dict['capabilities'] = {'network': ['sg', 'tso', 'tx']} + self.pci_device = pci_device.PciDevice.create(None, self.dev_dict) + self.assertEqual(self.pci_device.network_caps, ['sg', 'tso', 'tx']) + def test_update_device(self): self.pci_device = pci_device.PciDevice.create(None, dev_dict) self.pci_device.obj_reset_changes() diff --git a/nova/tests/unit/virt/libvirt/test_host.py b/nova/tests/unit/virt/libvirt/test_host.py index 9f82aefda027..a94dea48d3fc 100644 --- a/nova/tests/unit/virt/libvirt/test_host.py +++ b/nova/tests/unit/virt/libvirt/test_host.py @@ -1345,7 +1345,7 @@ Active: 8381604 kB "parent_ifname": "ens1", "capabilities": { "network": ["rx", "tx", "sg", "tso", "gso", "gro", "rxvlan", - "txvlan", "rxhash"], + "txvlan", "rxhash", "switchdev"], "sriov": {"pf_mac_address": "52:54:00:1e:59:c6", "vf_num": 1}, # Should be obtained from the parent PF in this case. diff --git a/releasenotes/notes/translate_vf_network_capabilities_to_port_binding-48abbfe0ce2923cf.yaml b/releasenotes/notes/translate_vf_network_capabilities_to_port_binding-48abbfe0ce2923cf.yaml new file mode 100644 index 000000000000..b5ee283c8c4c --- /dev/null +++ b/releasenotes/notes/translate_vf_network_capabilities_to_port_binding-48abbfe0ce2923cf.yaml @@ -0,0 +1,16 @@ +--- +fixes: + - | + Previously ``switchdev`` capabilities should be configured manually by a + user with admin privileges using port's binding profile. This blocked + regular users from managing ports with Open vSwitch hardware offloading + as providing write access to a port's binding profile to non-admin users + introduces security risks. For example, a binding profile may contain a + ``pci_slot`` definition, which denotes the host PCI address of the + device attached to the VM. A malicious user can use this parameter to + passthrough any host device to a guest, so it is impossible to provide + write access to a binding profile to regular users in many scenarios. + + This patch fixes this situation by translating VF capabilities reported + by Libvirt to Neutron port binding profiles. Other VF capabilities are + translated as well for possible future use.